@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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toolBuilder, ToolCategory, LogLevel, createLogger } from '@agentforge/core';
|
|
1
|
+
import { toolBuilder, ToolCategory, LogLevel, createLogger, createTool } from '@agentforge/core';
|
|
2
2
|
import { lookup } from 'dns/promises';
|
|
3
3
|
import net from 'net';
|
|
4
4
|
import axios11 from 'axios';
|
|
@@ -12,6 +12,7 @@ import neo4j, { auth, Integer, Node, Relationship, Path } from 'neo4j-driver';
|
|
|
12
12
|
import { sql } from 'drizzle-orm';
|
|
13
13
|
import { EventEmitter } from 'events';
|
|
14
14
|
import { Readable } from 'stream';
|
|
15
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
15
16
|
import { createHash, randomUUID } from 'crypto';
|
|
16
17
|
import { promises } from 'fs';
|
|
17
18
|
import * as path from 'path';
|
|
@@ -995,7 +996,7 @@ function getDefaultSlackClient() {
|
|
|
995
996
|
}
|
|
996
997
|
};
|
|
997
998
|
}
|
|
998
|
-
function createSendSlackMessageTool(getSlackClient,
|
|
999
|
+
function createSendSlackMessageTool(getSlackClient, logger20) {
|
|
999
1000
|
return toolBuilder().name("send-slack-message").description("Send a message to a Slack channel for team communication and notifications").category(ToolCategory.WEB).tags(["slack", "messaging", "communication"]).usageNotes(
|
|
1000
1001
|
"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."
|
|
1001
1002
|
).suggests(["get-slack-channels"]).schema(
|
|
@@ -1004,7 +1005,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
|
|
|
1004
1005
|
message: z.string().describe("Message content to send")
|
|
1005
1006
|
})
|
|
1006
1007
|
).implementSafe(async ({ channel, message }) => {
|
|
1007
|
-
|
|
1008
|
+
logger20.info("send-slack-message called", { channel, messageLength: message.length });
|
|
1008
1009
|
try {
|
|
1009
1010
|
const { client: slack, config } = getSlackClient();
|
|
1010
1011
|
const result = await slack.chat.postMessage({
|
|
@@ -1013,7 +1014,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
|
|
|
1013
1014
|
username: config.botName,
|
|
1014
1015
|
icon_emoji: config.botIcon
|
|
1015
1016
|
});
|
|
1016
|
-
|
|
1017
|
+
logger20.info("send-slack-message result", {
|
|
1017
1018
|
...result.channel ? { channel: result.channel } : {},
|
|
1018
1019
|
...result.ts ? { timestamp: result.ts } : {},
|
|
1019
1020
|
messageLength: message.length,
|
|
@@ -1026,7 +1027,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
|
|
|
1026
1027
|
message_id: result.ts
|
|
1027
1028
|
};
|
|
1028
1029
|
} catch (error) {
|
|
1029
|
-
|
|
1030
|
+
logger20.error("send-slack-message failed", {
|
|
1030
1031
|
channel,
|
|
1031
1032
|
error: error.message,
|
|
1032
1033
|
data: error.data
|
|
@@ -1035,7 +1036,7 @@ function createSendSlackMessageTool(getSlackClient, logger19) {
|
|
|
1035
1036
|
}
|
|
1036
1037
|
}).build();
|
|
1037
1038
|
}
|
|
1038
|
-
function createNotifySlackTool(getSlackClient,
|
|
1039
|
+
function createNotifySlackTool(getSlackClient, logger20) {
|
|
1039
1040
|
return toolBuilder().name("notify-slack").description("Send a notification to a Slack channel with optional @mentions for urgent alerts").category(ToolCategory.WEB).tags(["slack", "notification", "alert"]).usageNotes(
|
|
1040
1041
|
"Use this for urgent notifications that require @mentions. For general messages without mentions, use send-slack-message instead."
|
|
1041
1042
|
).suggests(["get-slack-channels"]).schema(
|
|
@@ -1045,7 +1046,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
|
|
|
1045
1046
|
mentions: z.array(z.string()).optional().describe("List of usernames to mention (without @)")
|
|
1046
1047
|
})
|
|
1047
1048
|
).implementSafe(async ({ channel, message, mentions = [] }) => {
|
|
1048
|
-
|
|
1049
|
+
logger20.info("notify-slack called", {
|
|
1049
1050
|
channel,
|
|
1050
1051
|
messageLength: message.length,
|
|
1051
1052
|
mentionCount: mentions.length
|
|
@@ -1059,7 +1060,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
|
|
|
1059
1060
|
username: config.botName,
|
|
1060
1061
|
icon_emoji: config.botIcon
|
|
1061
1062
|
});
|
|
1062
|
-
|
|
1063
|
+
logger20.info("notify-slack result", {
|
|
1063
1064
|
...result.channel ? { channel: result.channel } : {},
|
|
1064
1065
|
...result.ts ? { timestamp: result.ts } : {},
|
|
1065
1066
|
mentions: mentions.length
|
|
@@ -1073,7 +1074,7 @@ function createNotifySlackTool(getSlackClient, logger19) {
|
|
|
1073
1074
|
};
|
|
1074
1075
|
}).build();
|
|
1075
1076
|
}
|
|
1076
|
-
function createGetSlackChannelsTool(getSlackClient,
|
|
1077
|
+
function createGetSlackChannelsTool(getSlackClient, logger20) {
|
|
1077
1078
|
return toolBuilder().name("get-slack-channels").description("Get a list of available Slack channels to find the right channel for messaging").category(ToolCategory.WEB).tags(["slack", "channels", "list"]).usageNotes(
|
|
1078
1079
|
"Use this first to discover available channels before sending messages. Helps ensure you are sending to the correct channel."
|
|
1079
1080
|
).follows(["send-slack-message", "notify-slack"]).schema(
|
|
@@ -1081,7 +1082,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
|
|
|
1081
1082
|
include_private: z.boolean().optional().describe("Include private channels (default: false)")
|
|
1082
1083
|
})
|
|
1083
1084
|
).implementSafe(async ({ include_private = false }) => {
|
|
1084
|
-
|
|
1085
|
+
logger20.info("get-slack-channels called", { include_private });
|
|
1085
1086
|
const { client: slack } = getSlackClient();
|
|
1086
1087
|
const publicChannels = await slack.conversations.list({
|
|
1087
1088
|
types: "public_channel",
|
|
@@ -1095,7 +1096,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
|
|
|
1095
1096
|
});
|
|
1096
1097
|
allChannels = [...allChannels, ...privateChannels.channels || []];
|
|
1097
1098
|
}
|
|
1098
|
-
|
|
1099
|
+
logger20.info("get-slack-channels result", {
|
|
1099
1100
|
channelCount: allChannels.length,
|
|
1100
1101
|
includePrivate: include_private
|
|
1101
1102
|
});
|
|
@@ -1110,7 +1111,7 @@ function createGetSlackChannelsTool(getSlackClient, logger19) {
|
|
|
1110
1111
|
};
|
|
1111
1112
|
}).build();
|
|
1112
1113
|
}
|
|
1113
|
-
function createGetSlackMessagesTool(getSlackClient,
|
|
1114
|
+
function createGetSlackMessagesTool(getSlackClient, logger20) {
|
|
1114
1115
|
return toolBuilder().name("get-slack-messages").description("Retrieve message history from a Slack channel to read recent conversations").category(ToolCategory.WEB).tags(["slack", "messages", "history", "read"]).usageNotes(
|
|
1115
1116
|
"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)."
|
|
1116
1117
|
).suggests(["get-slack-channels"]).schema(
|
|
@@ -1119,7 +1120,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
|
|
|
1119
1120
|
limit: z.number().int().min(1).max(100).optional().describe("Number of messages to retrieve (default: 20, max: 100)")
|
|
1120
1121
|
})
|
|
1121
1122
|
).implementSafe(async ({ channel, limit = 20 }) => {
|
|
1122
|
-
|
|
1123
|
+
logger20.info("get-slack-messages called", { channel, limit });
|
|
1123
1124
|
try {
|
|
1124
1125
|
const { client: slack } = getSlackClient();
|
|
1125
1126
|
let channelId = channel;
|
|
@@ -1130,7 +1131,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
|
|
|
1130
1131
|
});
|
|
1131
1132
|
const found = channels.channels?.find((c) => c.name === channel);
|
|
1132
1133
|
if (!found) {
|
|
1133
|
-
|
|
1134
|
+
logger20.error("get-slack-messages: channel not found", { channel });
|
|
1134
1135
|
throw new Error(
|
|
1135
1136
|
`Channel '${channel}' not found. Use get-slack-channels to see available channels.`
|
|
1136
1137
|
);
|
|
@@ -1142,7 +1143,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
|
|
|
1142
1143
|
limit: Math.min(limit, 100)
|
|
1143
1144
|
// Cap at 100 for performance
|
|
1144
1145
|
});
|
|
1145
|
-
|
|
1146
|
+
logger20.info("get-slack-messages result", {
|
|
1146
1147
|
channel: channelId,
|
|
1147
1148
|
messageCount: result.messages?.length || 0,
|
|
1148
1149
|
limit
|
|
@@ -1160,7 +1161,7 @@ function createGetSlackMessagesTool(getSlackClient, logger19) {
|
|
|
1160
1161
|
})) || []
|
|
1161
1162
|
};
|
|
1162
1163
|
} catch (error) {
|
|
1163
|
-
|
|
1164
|
+
logger20.error("get-slack-messages failed", {
|
|
1164
1165
|
channel,
|
|
1165
1166
|
error: error.message,
|
|
1166
1167
|
data: error.data
|
|
@@ -1241,12 +1242,12 @@ function getAuthHeader() {
|
|
|
1241
1242
|
const auth2 = Buffer.from(`${ATLASSIAN_EMAIL}:${ATLASSIAN_API_KEY}`).toString("base64");
|
|
1242
1243
|
return `Basic ${auth2}`;
|
|
1243
1244
|
}
|
|
1244
|
-
function createSearchConfluenceTool(getAuth, getAuthHeader2,
|
|
1245
|
+
function createSearchConfluenceTool(getAuth, getAuthHeader2, logger20) {
|
|
1245
1246
|
return 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(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(z.object({
|
|
1246
1247
|
query: z.string().describe("Search query or CQL expression (e.g., 'payment processing' or 'space=BL3 AND title~payment')"),
|
|
1247
1248
|
limit: z.number().optional().describe("Maximum number of results to return (default: 10, max: 25)")
|
|
1248
1249
|
})).implement(async ({ query, limit = 10 }) => {
|
|
1249
|
-
|
|
1250
|
+
logger20.info("search-confluence called", { query, limit });
|
|
1250
1251
|
try {
|
|
1251
1252
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1252
1253
|
const response = await axios11.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content/search`, {
|
|
@@ -1271,13 +1272,13 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1271
1272
|
lastModified: page.version?.when || ""
|
|
1272
1273
|
}));
|
|
1273
1274
|
if (results.length === 0) {
|
|
1274
|
-
|
|
1275
|
+
logger20.warn("search-confluence returned NO RESULTS - this is a valid outcome, agent should not retry", {
|
|
1275
1276
|
query,
|
|
1276
1277
|
limit,
|
|
1277
1278
|
totalSize: response.data.totalSize
|
|
1278
1279
|
});
|
|
1279
1280
|
} else {
|
|
1280
|
-
|
|
1281
|
+
logger20.info("search-confluence result", {
|
|
1281
1282
|
query,
|
|
1282
1283
|
resultCount: results.length,
|
|
1283
1284
|
totalSize: response.data.totalSize,
|
|
@@ -1292,7 +1293,7 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1292
1293
|
results
|
|
1293
1294
|
});
|
|
1294
1295
|
} catch (error) {
|
|
1295
|
-
|
|
1296
|
+
logger20.error("search-confluence error", {
|
|
1296
1297
|
query,
|
|
1297
1298
|
error: error.response?.data?.message || error.message,
|
|
1298
1299
|
status: error.response?.status
|
|
@@ -1304,11 +1305,11 @@ function createSearchConfluenceTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1304
1305
|
}
|
|
1305
1306
|
}).build();
|
|
1306
1307
|
}
|
|
1307
|
-
function createGetConfluencePageTool(getAuth, getAuthHeader2,
|
|
1308
|
+
function createGetConfluencePageTool(getAuth, getAuthHeader2, logger20) {
|
|
1308
1309
|
return 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(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(z.object({
|
|
1309
1310
|
page_id: z.string().describe("The Confluence page ID (from search results)")
|
|
1310
1311
|
})).implement(async ({ page_id }) => {
|
|
1311
|
-
|
|
1312
|
+
logger20.info("get-confluence-page called", { page_id });
|
|
1312
1313
|
try {
|
|
1313
1314
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1314
1315
|
const response = await axios11.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content/${page_id}`, {
|
|
@@ -1321,7 +1322,7 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1321
1322
|
}
|
|
1322
1323
|
});
|
|
1323
1324
|
const page = response.data;
|
|
1324
|
-
|
|
1325
|
+
logger20.info("get-confluence-page result", {
|
|
1325
1326
|
page_id,
|
|
1326
1327
|
title: page.title,
|
|
1327
1328
|
space: page.space?.name,
|
|
@@ -1343,7 +1344,7 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1343
1344
|
}
|
|
1344
1345
|
});
|
|
1345
1346
|
} catch (error) {
|
|
1346
|
-
|
|
1347
|
+
logger20.error("get-confluence-page error", {
|
|
1347
1348
|
page_id,
|
|
1348
1349
|
error: error.response?.data?.message || error.message,
|
|
1349
1350
|
status: error.response?.status
|
|
@@ -1355,11 +1356,11 @@ function createGetConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1355
1356
|
}
|
|
1356
1357
|
}).build();
|
|
1357
1358
|
}
|
|
1358
|
-
function createListConfluenceSpacesTool(getAuth, getAuthHeader2,
|
|
1359
|
+
function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger20) {
|
|
1359
1360
|
return 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(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(z.object({
|
|
1360
1361
|
limit: z.number().optional().describe("Maximum number of spaces to return (default: 25)")
|
|
1361
1362
|
})).implement(async ({ limit = 25 }) => {
|
|
1362
|
-
|
|
1363
|
+
logger20.info("list-confluence-spaces called", { limit });
|
|
1363
1364
|
try {
|
|
1364
1365
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1365
1366
|
const response = await axios11.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/space`, {
|
|
@@ -1378,7 +1379,7 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1378
1379
|
description: space.description?.plain?.value || "",
|
|
1379
1380
|
url: `${ATLASSIAN_SITE_URL}/wiki${space._links.webui}`
|
|
1380
1381
|
}));
|
|
1381
|
-
|
|
1382
|
+
logger20.info("list-confluence-spaces result", {
|
|
1382
1383
|
spaceCount: spaces.length,
|
|
1383
1384
|
spaceKeys: spaces.map((s) => s.key).slice(0, 5)
|
|
1384
1385
|
// Log first 5 space keys
|
|
@@ -1389,7 +1390,7 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1389
1390
|
spaces
|
|
1390
1391
|
});
|
|
1391
1392
|
} catch (error) {
|
|
1392
|
-
|
|
1393
|
+
logger20.error("list-confluence-spaces error", {
|
|
1393
1394
|
error: error.response?.data?.message || error.message,
|
|
1394
1395
|
status: error.response?.status
|
|
1395
1396
|
});
|
|
@@ -1400,12 +1401,12 @@ function createListConfluenceSpacesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1400
1401
|
}
|
|
1401
1402
|
}).build();
|
|
1402
1403
|
}
|
|
1403
|
-
function createGetSpacePagesTool(getAuth, getAuthHeader2,
|
|
1404
|
+
function createGetSpacePagesTool(getAuth, getAuthHeader2, logger20) {
|
|
1404
1405
|
return 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(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(z.object({
|
|
1405
1406
|
space_key: z.string().describe("The space key (e.g., 'AI', 'BL3', 'FIN')"),
|
|
1406
1407
|
limit: z.number().optional().describe("Maximum number of pages to return (default: 25)")
|
|
1407
1408
|
})).implement(async ({ space_key, limit = 25 }) => {
|
|
1408
|
-
|
|
1409
|
+
logger20.info("get-space-pages called", { space_key, limit });
|
|
1409
1410
|
try {
|
|
1410
1411
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1411
1412
|
const response = await axios11.get(`${ATLASSIAN_SITE_URL}/wiki/rest/api/content`, {
|
|
@@ -1427,12 +1428,12 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1427
1428
|
lastModified: page.version?.when || ""
|
|
1428
1429
|
}));
|
|
1429
1430
|
if (pages.length === 0) {
|
|
1430
|
-
|
|
1431
|
+
logger20.warn("get-space-pages returned NO PAGES - this is a valid outcome, agent should not retry", {
|
|
1431
1432
|
space_key,
|
|
1432
1433
|
limit
|
|
1433
1434
|
});
|
|
1434
1435
|
} else {
|
|
1435
|
-
|
|
1436
|
+
logger20.info("get-space-pages result", {
|
|
1436
1437
|
space_key,
|
|
1437
1438
|
pageCount: pages.length,
|
|
1438
1439
|
titles: pages.map((p) => p.title).slice(0, 3)
|
|
@@ -1446,7 +1447,7 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1446
1447
|
pages
|
|
1447
1448
|
});
|
|
1448
1449
|
} catch (error) {
|
|
1449
|
-
|
|
1450
|
+
logger20.error("get-space-pages error", {
|
|
1450
1451
|
space_key,
|
|
1451
1452
|
error: error.response?.data?.message || error.message,
|
|
1452
1453
|
status: error.response?.status
|
|
@@ -1458,14 +1459,14 @@ function createGetSpacePagesTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1458
1459
|
}
|
|
1459
1460
|
}).build();
|
|
1460
1461
|
}
|
|
1461
|
-
function createCreateConfluencePageTool(getAuth, getAuthHeader2,
|
|
1462
|
+
function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger20) {
|
|
1462
1463
|
return 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(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(z.object({
|
|
1463
1464
|
space_key: z.string().describe("The space key where the page will be created (e.g., 'AI', 'BL3')"),
|
|
1464
1465
|
title: z.string().describe("The title of the new page"),
|
|
1465
1466
|
content: z.string().describe("The page content in HTML format (Confluence storage format)"),
|
|
1466
1467
|
parent_page_id: z.string().optional().describe("Optional parent page ID to create this as a child page")
|
|
1467
1468
|
})).implement(async ({ space_key, title, content, parent_page_id }) => {
|
|
1468
|
-
|
|
1469
|
+
logger20.info("create-confluence-page called", { space_key, title, hasParent: !!parent_page_id });
|
|
1469
1470
|
try {
|
|
1470
1471
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1471
1472
|
const pageData = {
|
|
@@ -1492,7 +1493,7 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1492
1493
|
}
|
|
1493
1494
|
}
|
|
1494
1495
|
);
|
|
1495
|
-
|
|
1496
|
+
logger20.info("create-confluence-page result", {
|
|
1496
1497
|
page_id: response.data.id,
|
|
1497
1498
|
title: response.data.title,
|
|
1498
1499
|
space: space_key
|
|
@@ -1508,7 +1509,7 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1508
1509
|
}
|
|
1509
1510
|
});
|
|
1510
1511
|
} catch (error) {
|
|
1511
|
-
|
|
1512
|
+
logger20.error("create-confluence-page error", {
|
|
1512
1513
|
space_key,
|
|
1513
1514
|
title,
|
|
1514
1515
|
error: error.response?.data?.message || error.message,
|
|
@@ -1521,13 +1522,13 @@ function createCreateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1521
1522
|
}
|
|
1522
1523
|
}).build();
|
|
1523
1524
|
}
|
|
1524
|
-
function createUpdateConfluencePageTool(getAuth, getAuthHeader2,
|
|
1525
|
+
function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger20) {
|
|
1525
1526
|
return toolBuilder().name("update-confluence-page").description("Update an existing Confluence page's content. Requires page ID, new title, and new content.").category(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(z.object({
|
|
1526
1527
|
page_id: z.string().describe("The ID of the page to update"),
|
|
1527
1528
|
title: z.string().describe("The new title for the page"),
|
|
1528
1529
|
content: z.string().describe("The new content in HTML format (Confluence storage format)")
|
|
1529
1530
|
})).implement(async ({ page_id, title, content }) => {
|
|
1530
|
-
|
|
1531
|
+
logger20.info("update-confluence-page called", { page_id, title });
|
|
1531
1532
|
try {
|
|
1532
1533
|
const { ATLASSIAN_SITE_URL } = getAuth();
|
|
1533
1534
|
const getResponse = await axios11.get(
|
|
@@ -1560,7 +1561,7 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1560
1561
|
}
|
|
1561
1562
|
}
|
|
1562
1563
|
);
|
|
1563
|
-
|
|
1564
|
+
logger20.info("update-confluence-page result", {
|
|
1564
1565
|
page_id,
|
|
1565
1566
|
title: updateResponse.data.title,
|
|
1566
1567
|
previousVersion: currentVersion,
|
|
@@ -1577,7 +1578,7 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1577
1578
|
}
|
|
1578
1579
|
});
|
|
1579
1580
|
} catch (error) {
|
|
1580
|
-
|
|
1581
|
+
logger20.error("update-confluence-page error", {
|
|
1581
1582
|
page_id,
|
|
1582
1583
|
title,
|
|
1583
1584
|
error: error.response?.data?.message || error.message,
|
|
@@ -1590,12 +1591,12 @@ function createUpdateConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1590
1591
|
}
|
|
1591
1592
|
}).build();
|
|
1592
1593
|
}
|
|
1593
|
-
function createArchiveConfluencePageTool(getAuth, getAuthHeader2,
|
|
1594
|
+
function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger20) {
|
|
1594
1595
|
return 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(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(z.object({
|
|
1595
1596
|
page_id: z.string().describe("The ID of the page to archive"),
|
|
1596
1597
|
reason: z.string().optional().describe("Optional reason for archiving (for audit trail)")
|
|
1597
1598
|
})).implement(async ({ page_id, reason }) => {
|
|
1598
|
-
|
|
1599
|
+
logger20.info("archive-confluence-page called", {
|
|
1599
1600
|
page_id,
|
|
1600
1601
|
...reason ? { reason } : {}
|
|
1601
1602
|
});
|
|
@@ -1629,7 +1630,7 @@ function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1629
1630
|
}
|
|
1630
1631
|
}
|
|
1631
1632
|
);
|
|
1632
|
-
|
|
1633
|
+
logger20.info("archive-confluence-page result", {
|
|
1633
1634
|
page_id,
|
|
1634
1635
|
title: pageData.title,
|
|
1635
1636
|
previousVersion: currentVersion,
|
|
@@ -1647,7 +1648,7 @@ function createArchiveConfluencePageTool(getAuth, getAuthHeader2, logger19) {
|
|
|
1647
1648
|
}
|
|
1648
1649
|
});
|
|
1649
1650
|
} catch (error) {
|
|
1650
|
-
|
|
1651
|
+
logger20.error("archive-confluence-page error", {
|
|
1651
1652
|
page_id,
|
|
1652
1653
|
error: error.response?.data?.message || error.message,
|
|
1653
1654
|
status: error.response?.status
|
|
@@ -4157,11 +4158,41 @@ function getInstallationInstructions(vendor) {
|
|
|
4157
4158
|
|
|
4158
4159
|
// src/data/relational/utils/sql-sanitizer.ts
|
|
4159
4160
|
var DANGEROUS_SQL_STATEMENT_PATTERN = /^(create|drop|truncate|alter)\b/i;
|
|
4161
|
+
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;
|
|
4162
|
+
var AUTOCOMMIT_STATEMENT_PATTERN = /^set\s+(?:(?:session|local|global)\s+|@@(?:(?:session|local|global)\.)?)?autocommit\b/i;
|
|
4163
|
+
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;
|
|
4164
|
+
var MYSQL_INDIRECT_EXECUTION_STATEMENT_PATTERN = /^(?:call\b|prepare\b|execute(?:\s+immediate)?\b|deallocate\s+prepare\b)/i;
|
|
4165
|
+
var MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE = "Transaction control statements are not allowed in scoped Tools.";
|
|
4160
4166
|
var NUMBERED_PLACEHOLDER_PATTERN = /\$(\d+)/;
|
|
4161
4167
|
var QUESTION_PLACEHOLDER_PATTERN = /\?/;
|
|
4162
4168
|
var NAMED_PLACEHOLDER_PATTERN = /(?<!:):[a-zA-Z_][a-zA-Z0-9_]*/;
|
|
4163
4169
|
var PARAMETER_REQUIRED_PATTERN = /^(insert|update|delete)\b/i;
|
|
4164
4170
|
var MUTATION_PATTERN = /\b(insert|update|delete)\b/i;
|
|
4171
|
+
function sqlStripOptions(vendor, rejectExecutableComments = false, backslashEscapes = vendor === "mysql") {
|
|
4172
|
+
return {
|
|
4173
|
+
backslashEscapes,
|
|
4174
|
+
hashComments: vendor === "mysql",
|
|
4175
|
+
mysqlDashDashComments: vendor === "mysql",
|
|
4176
|
+
nestedBlockComments: vendor === "postgresql",
|
|
4177
|
+
postgresEscapeStrings: vendor === "postgresql",
|
|
4178
|
+
rejectExecutableComments
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
function isPostgresEscapeStringQuote(sqlString, quoteIndex) {
|
|
4182
|
+
const prefixIndex = quoteIndex - 1;
|
|
4183
|
+
if (prefixIndex < 0 || !/[eE]/.test(sqlString[prefixIndex])) {
|
|
4184
|
+
return false;
|
|
4185
|
+
}
|
|
4186
|
+
const beforePrefix = prefixIndex - 1;
|
|
4187
|
+
return beforePrefix < 0 || !/[A-Za-z0-9_$]/.test(sqlString[beforePrefix]);
|
|
4188
|
+
}
|
|
4189
|
+
function isWhitespaceOrControlCharacter(value) {
|
|
4190
|
+
if (!value) {
|
|
4191
|
+
return false;
|
|
4192
|
+
}
|
|
4193
|
+
const codePoint = value.charCodeAt(0);
|
|
4194
|
+
return /\s/u.test(value) || codePoint < 32 || codePoint === 127;
|
|
4195
|
+
}
|
|
4165
4196
|
function stripSqlCommentsAndStrings(sqlString, options) {
|
|
4166
4197
|
const { backslashEscapes } = options;
|
|
4167
4198
|
let result = "";
|
|
@@ -4170,7 +4201,17 @@ function stripSqlCommentsAndStrings(sqlString, options) {
|
|
|
4170
4201
|
while (i < len) {
|
|
4171
4202
|
const ch = sqlString[i];
|
|
4172
4203
|
const next = i + 1 < len ? sqlString[i + 1] : "";
|
|
4173
|
-
if (
|
|
4204
|
+
if (options.hashComments && ch === "#") {
|
|
4205
|
+
result += " ";
|
|
4206
|
+
i += 1;
|
|
4207
|
+
while (i < len && sqlString[i] !== "\n") {
|
|
4208
|
+
i += 1;
|
|
4209
|
+
}
|
|
4210
|
+
continue;
|
|
4211
|
+
}
|
|
4212
|
+
const afterDoubleDash = i + 2 < len ? sqlString[i + 2] : "";
|
|
4213
|
+
const doubleDashStartsComment = ch === "-" && next === "-" && (!options.mysqlDashDashComments || isWhitespaceOrControlCharacter(afterDoubleDash));
|
|
4214
|
+
if (doubleDashStartsComment) {
|
|
4174
4215
|
result += " ";
|
|
4175
4216
|
i += 2;
|
|
4176
4217
|
while (i < len && sqlString[i] !== "\n") {
|
|
@@ -4179,12 +4220,23 @@ function stripSqlCommentsAndStrings(sqlString, options) {
|
|
|
4179
4220
|
continue;
|
|
4180
4221
|
}
|
|
4181
4222
|
if (ch === "/" && next === "*") {
|
|
4223
|
+
const executableComment = sqlString[i + 2] === "!" || sqlString[i + 2]?.toLowerCase() === "m" && sqlString[i + 3] === "!";
|
|
4224
|
+
if (options.rejectExecutableComments && executableComment) {
|
|
4225
|
+
throw new Error(MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE);
|
|
4226
|
+
}
|
|
4182
4227
|
result += " ";
|
|
4183
4228
|
i += 2;
|
|
4184
|
-
|
|
4229
|
+
let depth = 1;
|
|
4230
|
+
while (i < len && depth > 0) {
|
|
4231
|
+
if (options.nestedBlockComments && sqlString[i] === "/" && i + 1 < len && sqlString[i + 1] === "*") {
|
|
4232
|
+
depth += 1;
|
|
4233
|
+
i += 2;
|
|
4234
|
+
continue;
|
|
4235
|
+
}
|
|
4185
4236
|
if (sqlString[i] === "*" && i + 1 < len && sqlString[i + 1] === "/") {
|
|
4237
|
+
depth -= 1;
|
|
4186
4238
|
i += 2;
|
|
4187
|
-
|
|
4239
|
+
continue;
|
|
4188
4240
|
}
|
|
4189
4241
|
i += 1;
|
|
4190
4242
|
}
|
|
@@ -4192,9 +4244,10 @@ function stripSqlCommentsAndStrings(sqlString, options) {
|
|
|
4192
4244
|
}
|
|
4193
4245
|
if (ch === "'") {
|
|
4194
4246
|
result += "''";
|
|
4247
|
+
const singleQuoteBackslashEscapes = backslashEscapes || options.postgresEscapeStrings && isPostgresEscapeStringQuote(sqlString, i);
|
|
4195
4248
|
i += 1;
|
|
4196
4249
|
while (i < len) {
|
|
4197
|
-
if (
|
|
4250
|
+
if (singleQuoteBackslashEscapes && sqlString[i] === "\\" && i + 1 < len) {
|
|
4198
4251
|
i += 2;
|
|
4199
4252
|
continue;
|
|
4200
4253
|
}
|
|
@@ -4252,6 +4305,20 @@ function stripSqlCommentsAndStrings(sqlString, options) {
|
|
|
4252
4305
|
}
|
|
4253
4306
|
return result;
|
|
4254
4307
|
}
|
|
4308
|
+
function strippedSqlVariants(sqlString, vendor, rejectExecutableComments = false, analyzeMysqlNoBackslashEscapes = false) {
|
|
4309
|
+
const variants = [
|
|
4310
|
+
stripSqlCommentsAndStrings(sqlString, sqlStripOptions(vendor, rejectExecutableComments))
|
|
4311
|
+
];
|
|
4312
|
+
if (vendor === "mysql" && analyzeMysqlNoBackslashEscapes) {
|
|
4313
|
+
variants.push(
|
|
4314
|
+
stripSqlCommentsAndStrings(
|
|
4315
|
+
sqlString,
|
|
4316
|
+
sqlStripOptions(vendor, rejectExecutableComments, false)
|
|
4317
|
+
)
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
4320
|
+
return [...new Set(variants)];
|
|
4321
|
+
}
|
|
4255
4322
|
function nextNonWhitespaceChar(sqlString, index) {
|
|
4256
4323
|
for (let i = index; i < sqlString.length; i += 1) {
|
|
4257
4324
|
if (!/\s/.test(sqlString[i])) {
|
|
@@ -4301,6 +4368,16 @@ function hasParameters(params) {
|
|
|
4301
4368
|
}
|
|
4302
4369
|
return Object.keys(params).length > 0;
|
|
4303
4370
|
}
|
|
4371
|
+
function sqlStatements(sqlString, vendor, rejectExecutableComments = false, analyzeMysqlNoBackslashEscapes = false) {
|
|
4372
|
+
return strippedSqlVariants(
|
|
4373
|
+
sqlString,
|
|
4374
|
+
vendor,
|
|
4375
|
+
rejectExecutableComments,
|
|
4376
|
+
analyzeMysqlNoBackslashEscapes
|
|
4377
|
+
).flatMap(
|
|
4378
|
+
(normalized) => normalized.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0)
|
|
4379
|
+
);
|
|
4380
|
+
}
|
|
4304
4381
|
function validateSqlString(sqlString, vendor) {
|
|
4305
4382
|
if (typeof sqlString !== "string" || sqlString.trim().length === 0) {
|
|
4306
4383
|
throw new Error("SQL query must not be empty");
|
|
@@ -4308,18 +4385,20 @@ function validateSqlString(sqlString, vendor) {
|
|
|
4308
4385
|
if (sqlString.includes("\0")) {
|
|
4309
4386
|
throw new Error("SQL query contains null bytes");
|
|
4310
4387
|
}
|
|
4311
|
-
const
|
|
4312
|
-
backslashEscapes: vendor === "mysql"
|
|
4313
|
-
});
|
|
4314
|
-
const statements = normalizedForSafetyCheck.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
|
|
4388
|
+
const statements = sqlStatements(sqlString, vendor);
|
|
4315
4389
|
if (statements.some((statement) => DANGEROUS_SQL_STATEMENT_PATTERN.test(statement))) {
|
|
4316
4390
|
throw new Error("Detected dangerous SQL operation. CREATE, DROP, TRUNCATE, and ALTER are not allowed.");
|
|
4317
4391
|
}
|
|
4318
4392
|
}
|
|
4393
|
+
function validateManagedTransactionSql(sqlString, vendor) {
|
|
4394
|
+
if (sqlStatements(sqlString, vendor, true, true).some(
|
|
4395
|
+
(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))
|
|
4396
|
+
)) {
|
|
4397
|
+
throw new Error(MANAGED_TRANSACTION_CONTROL_ERROR_MESSAGE);
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4319
4400
|
function enforceParameterizedQueryUsage(sqlString, params, vendor) {
|
|
4320
|
-
const normalizedForAnalysis = stripSqlCommentsAndStrings(sqlString,
|
|
4321
|
-
backslashEscapes: vendor === "mysql"
|
|
4322
|
-
});
|
|
4401
|
+
const normalizedForAnalysis = stripSqlCommentsAndStrings(sqlString, sqlStripOptions(vendor));
|
|
4323
4402
|
const normalized = normalizedForAnalysis.trim().toLowerCase();
|
|
4324
4403
|
const hasPlaceholders = hasSqlPlaceholders(normalizedForAnalysis, vendor);
|
|
4325
4404
|
const hasParams = hasParameters(params);
|
|
@@ -4378,19 +4457,19 @@ var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
|
4378
4457
|
ConnectionState2["ERROR"] = "error";
|
|
4379
4458
|
return ConnectionState2;
|
|
4380
4459
|
})(ConnectionState || {});
|
|
4381
|
-
function cancelPendingReconnection(timer,
|
|
4460
|
+
function cancelPendingReconnection(timer, logger20, vendor, message) {
|
|
4382
4461
|
if (!timer) {
|
|
4383
4462
|
return null;
|
|
4384
4463
|
}
|
|
4385
|
-
|
|
4464
|
+
logger20.debug(message, { vendor });
|
|
4386
4465
|
clearTimeout(timer);
|
|
4387
4466
|
return null;
|
|
4388
4467
|
}
|
|
4389
|
-
async function waitForInFlightConnection(promise,
|
|
4468
|
+
async function waitForInFlightConnection(promise, logger20, vendor, phase) {
|
|
4390
4469
|
if (!promise) {
|
|
4391
4470
|
return null;
|
|
4392
4471
|
}
|
|
4393
|
-
|
|
4472
|
+
logger20.debug(`Waiting for in-flight connection attempt to complete before ${phase}`, {
|
|
4394
4473
|
vendor
|
|
4395
4474
|
});
|
|
4396
4475
|
try {
|
|
@@ -4407,9 +4486,9 @@ function shutdownClient(vendor, client) {
|
|
|
4407
4486
|
client.close();
|
|
4408
4487
|
}
|
|
4409
4488
|
}
|
|
4410
|
-
function scheduleReconnection(context,
|
|
4489
|
+
function scheduleReconnection(context, logger20) {
|
|
4411
4490
|
if (context.reconnectionConfig.maxAttempts > 0 && context.reconnectionAttempts >= context.reconnectionConfig.maxAttempts) {
|
|
4412
|
-
|
|
4491
|
+
logger20.error("Max reconnection attempts reached", {
|
|
4413
4492
|
vendor: context.vendor,
|
|
4414
4493
|
attempts: context.reconnectionAttempts,
|
|
4415
4494
|
maxAttempts: context.reconnectionConfig.maxAttempts
|
|
@@ -4423,7 +4502,7 @@ function scheduleReconnection(context, logger19) {
|
|
|
4423
4502
|
const nextAttempt = context.reconnectionAttempts + 1;
|
|
4424
4503
|
context.setReconnectionAttempts(nextAttempt);
|
|
4425
4504
|
context.setState("reconnecting" /* RECONNECTING */);
|
|
4426
|
-
|
|
4505
|
+
logger20.info("Scheduling reconnection attempt", {
|
|
4427
4506
|
vendor: context.vendor,
|
|
4428
4507
|
attempt: nextAttempt,
|
|
4429
4508
|
maxAttempts: context.reconnectionConfig.maxAttempts,
|
|
@@ -4437,7 +4516,7 @@ function scheduleReconnection(context, logger19) {
|
|
|
4437
4516
|
const timer = setTimeout(async () => {
|
|
4438
4517
|
context.setReconnectionTimer(null);
|
|
4439
4518
|
try {
|
|
4440
|
-
|
|
4519
|
+
logger20.info("Attempting reconnection", {
|
|
4441
4520
|
vendor: context.vendor,
|
|
4442
4521
|
attempt: nextAttempt
|
|
4443
4522
|
});
|
|
@@ -4447,7 +4526,7 @@ function scheduleReconnection(context, logger19) {
|
|
|
4447
4526
|
context.setConnectPromise(promise);
|
|
4448
4527
|
await promise;
|
|
4449
4528
|
} catch (error) {
|
|
4450
|
-
|
|
4529
|
+
logger20.error("Reconnection attempt failed", {
|
|
4451
4530
|
vendor: context.vendor,
|
|
4452
4531
|
attempt: nextAttempt,
|
|
4453
4532
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -4458,17 +4537,17 @@ function scheduleReconnection(context, logger19) {
|
|
|
4458
4537
|
}
|
|
4459
4538
|
|
|
4460
4539
|
// src/data/relational/connection/connection-cleanup.ts
|
|
4461
|
-
async function cleanupConnectionResources(vendor, client, clearConnection,
|
|
4540
|
+
async function cleanupConnectionResources(vendor, client, clearConnection, logger20) {
|
|
4462
4541
|
if (!client) {
|
|
4463
4542
|
return;
|
|
4464
4543
|
}
|
|
4465
|
-
|
|
4544
|
+
logger20.debug("Cleaning up cancelled connection", {
|
|
4466
4545
|
vendor
|
|
4467
4546
|
});
|
|
4468
4547
|
try {
|
|
4469
4548
|
await shutdownClient(vendor, client);
|
|
4470
4549
|
} catch (error) {
|
|
4471
|
-
|
|
4550
|
+
logger20.debug("Error during cancelled connection cleanup", {
|
|
4472
4551
|
vendor,
|
|
4473
4552
|
error: error instanceof Error ? error.message : String(error)
|
|
4474
4553
|
});
|
|
@@ -4476,10 +4555,10 @@ async function cleanupConnectionResources(vendor, client, clearConnection, logge
|
|
|
4476
4555
|
clearConnection();
|
|
4477
4556
|
}
|
|
4478
4557
|
}
|
|
4479
|
-
async function closeManagedConnection(runtime,
|
|
4558
|
+
async function closeManagedConnection(runtime, logger20) {
|
|
4480
4559
|
const client = runtime.getClient();
|
|
4481
4560
|
if (client) {
|
|
4482
|
-
|
|
4561
|
+
logger20.info("Closing database connection", {
|
|
4483
4562
|
vendor: runtime.vendor,
|
|
4484
4563
|
state: runtime.getState()
|
|
4485
4564
|
});
|
|
@@ -4487,12 +4566,12 @@ async function closeManagedConnection(runtime, logger19) {
|
|
|
4487
4566
|
await shutdownClient(runtime.vendor, client);
|
|
4488
4567
|
runtime.setState("disconnected" /* DISCONNECTED */);
|
|
4489
4568
|
runtime.emitDisconnected();
|
|
4490
|
-
|
|
4569
|
+
logger20.debug("Database connection closed successfully", {
|
|
4491
4570
|
vendor: runtime.vendor,
|
|
4492
4571
|
state: runtime.getState()
|
|
4493
4572
|
});
|
|
4494
4573
|
} catch (error) {
|
|
4495
|
-
|
|
4574
|
+
logger20.error("Error closing database connection", {
|
|
4496
4575
|
vendor: runtime.vendor,
|
|
4497
4576
|
error: error instanceof Error ? error.message : String(error),
|
|
4498
4577
|
state: runtime.getState()
|
|
@@ -4665,11 +4744,11 @@ async function initializeSQLiteConnection(connection) {
|
|
|
4665
4744
|
}
|
|
4666
4745
|
|
|
4667
4746
|
// src/data/relational/connection/connection-initialization.ts
|
|
4668
|
-
async function initializeManagedConnection(runtime,
|
|
4747
|
+
async function initializeManagedConnection(runtime, logger20) {
|
|
4669
4748
|
const startTime = Date.now();
|
|
4670
4749
|
const currentGeneration = runtime.getConnectionGeneration();
|
|
4671
4750
|
if (runtime.getState() === "connected" /* CONNECTED */ && runtime.getClient()) {
|
|
4672
|
-
|
|
4751
|
+
logger20.warn("Re-initializing an already connected manager; emitting disconnected before cleanup", {
|
|
4673
4752
|
vendor: runtime.vendor
|
|
4674
4753
|
});
|
|
4675
4754
|
runtime.setState("disconnected" /* DISCONNECTED */);
|
|
@@ -4681,11 +4760,11 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4681
4760
|
runtime.setClient(null);
|
|
4682
4761
|
runtime.setDb(null);
|
|
4683
4762
|
},
|
|
4684
|
-
|
|
4763
|
+
logger20
|
|
4685
4764
|
);
|
|
4686
4765
|
}
|
|
4687
4766
|
runtime.setState("connecting" /* CONNECTING */);
|
|
4688
|
-
|
|
4767
|
+
logger20.info("Initializing database connection", {
|
|
4689
4768
|
vendor: runtime.vendor,
|
|
4690
4769
|
connectionType: typeof runtime.config.connection,
|
|
4691
4770
|
state: runtime.getState()
|
|
@@ -4695,7 +4774,7 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4695
4774
|
runtime.setClient(initialized.client);
|
|
4696
4775
|
runtime.setDb(initialized.db);
|
|
4697
4776
|
if (currentGeneration !== runtime.getConnectionGeneration()) {
|
|
4698
|
-
|
|
4777
|
+
logger20.debug("Connection cancelled during initialization", {
|
|
4699
4778
|
vendor: runtime.vendor,
|
|
4700
4779
|
currentGeneration,
|
|
4701
4780
|
newGeneration: runtime.getConnectionGeneration()
|
|
@@ -4707,18 +4786,18 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4707
4786
|
runtime.setClient(null);
|
|
4708
4787
|
runtime.setDb(null);
|
|
4709
4788
|
},
|
|
4710
|
-
|
|
4789
|
+
logger20
|
|
4711
4790
|
);
|
|
4712
4791
|
runtime.setState("disconnected" /* DISCONNECTED */);
|
|
4713
4792
|
throw new Error("Connection cancelled during initialization");
|
|
4714
4793
|
}
|
|
4715
|
-
|
|
4794
|
+
logger20.debug("Validating connection health", { vendor: runtime.vendor });
|
|
4716
4795
|
const healthy = await runtime.isHealthy();
|
|
4717
4796
|
if (!healthy) {
|
|
4718
4797
|
throw new Error(`Failed to establish healthy connection to ${runtime.vendor} database`);
|
|
4719
4798
|
}
|
|
4720
4799
|
if (currentGeneration !== runtime.getConnectionGeneration()) {
|
|
4721
|
-
|
|
4800
|
+
logger20.debug("Connection cancelled during health check", {
|
|
4722
4801
|
vendor: runtime.vendor,
|
|
4723
4802
|
currentGeneration,
|
|
4724
4803
|
newGeneration: runtime.getConnectionGeneration()
|
|
@@ -4730,7 +4809,7 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4730
4809
|
runtime.setClient(null);
|
|
4731
4810
|
runtime.setDb(null);
|
|
4732
4811
|
},
|
|
4733
|
-
|
|
4812
|
+
logger20
|
|
4734
4813
|
);
|
|
4735
4814
|
runtime.setState("disconnected" /* DISCONNECTED */);
|
|
4736
4815
|
throw new Error("Connection cancelled during health check");
|
|
@@ -4738,7 +4817,7 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4738
4817
|
runtime.setState("connected" /* CONNECTED */);
|
|
4739
4818
|
runtime.emitConnected();
|
|
4740
4819
|
runtime.resetReconnectionAttempts();
|
|
4741
|
-
|
|
4820
|
+
logger20.info("Database connection initialized successfully", {
|
|
4742
4821
|
vendor: runtime.vendor,
|
|
4743
4822
|
duration: Date.now() - startTime,
|
|
4744
4823
|
state: runtime.getState()
|
|
@@ -4759,7 +4838,7 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4759
4838
|
if (runtime.errorListenerCount() > 0) {
|
|
4760
4839
|
runtime.emitError(normalizedError);
|
|
4761
4840
|
}
|
|
4762
|
-
|
|
4841
|
+
logger20.error(errorMessage, {
|
|
4763
4842
|
vendor: runtime.vendor,
|
|
4764
4843
|
error: normalizedError.message,
|
|
4765
4844
|
duration: Date.now() - startTime,
|
|
@@ -4772,13 +4851,13 @@ async function initializeManagedConnection(runtime, logger19) {
|
|
|
4772
4851
|
runtime.setClient(null);
|
|
4773
4852
|
runtime.setDb(null);
|
|
4774
4853
|
},
|
|
4775
|
-
|
|
4854
|
+
logger20
|
|
4776
4855
|
);
|
|
4777
4856
|
if (runtime.getReconnectionConfig().enabled && currentGeneration === runtime.getConnectionGeneration()) {
|
|
4778
4857
|
runtime.scheduleReconnection();
|
|
4779
4858
|
}
|
|
4780
4859
|
} else {
|
|
4781
|
-
|
|
4860
|
+
logger20.debug("Connection initialization cancelled", {
|
|
4782
4861
|
vendor: runtime.vendor,
|
|
4783
4862
|
duration: Date.now() - startTime,
|
|
4784
4863
|
state: runtime.getState()
|
|
@@ -4796,17 +4875,17 @@ function isSqliteNonQueryError(error) {
|
|
|
4796
4875
|
const hasNonQueryMessage = error.message.includes("does not return data");
|
|
4797
4876
|
return isSqliteOrTypeError && hasNonQueryMessage;
|
|
4798
4877
|
}
|
|
4799
|
-
async function checkConnectionHealth(args,
|
|
4878
|
+
async function checkConnectionHealth(args, logger20) {
|
|
4800
4879
|
if (!args.db || !args.client) {
|
|
4801
|
-
|
|
4880
|
+
logger20.debug("Health check failed: connection not initialized", { vendor: args.vendor });
|
|
4802
4881
|
return false;
|
|
4803
4882
|
}
|
|
4804
4883
|
try {
|
|
4805
4884
|
await args.execute(sql`SELECT 1`);
|
|
4806
|
-
|
|
4885
|
+
logger20.debug("Health check passed", { vendor: args.vendor });
|
|
4807
4886
|
return true;
|
|
4808
4887
|
} catch (error) {
|
|
4809
|
-
|
|
4888
|
+
logger20.debug("Health check failed", {
|
|
4810
4889
|
vendor: args.vendor,
|
|
4811
4890
|
error: error instanceof Error ? error.message : String(error)
|
|
4812
4891
|
});
|
|
@@ -4849,8 +4928,8 @@ function getConnectionPoolMetrics(vendor, client) {
|
|
|
4849
4928
|
}
|
|
4850
4929
|
|
|
4851
4930
|
// src/data/relational/connection/query-execution.ts
|
|
4852
|
-
async function executeQuery(context, query,
|
|
4853
|
-
|
|
4931
|
+
async function executeQuery(context, query, logger20) {
|
|
4932
|
+
logger20.debug("Executing SQL query", {
|
|
4854
4933
|
vendor: context.vendor
|
|
4855
4934
|
});
|
|
4856
4935
|
if (context.vendor === "sqlite") {
|
|
@@ -4901,7 +4980,7 @@ async function executeInDedicatedConnection(context, callback) {
|
|
|
4901
4980
|
try {
|
|
4902
4981
|
const { drizzle } = await import('drizzle-orm/node-postgres');
|
|
4903
4982
|
const sessionDb = drizzle({ client: poolClient });
|
|
4904
|
-
return await callback((query) => sessionDb.execute(query));
|
|
4983
|
+
return await callback(async (query) => sessionDb.execute(query));
|
|
4905
4984
|
} finally {
|
|
4906
4985
|
poolClient.release();
|
|
4907
4986
|
}
|
|
@@ -5226,7 +5305,7 @@ function buildParameterizedQuery(sqlString, params) {
|
|
|
5226
5305
|
return sqlChunks.length > 0 ? sql.join(sqlChunks, sql.raw("")) : sql.raw(sqlString);
|
|
5227
5306
|
}
|
|
5228
5307
|
}
|
|
5229
|
-
async function executeQuery2(
|
|
5308
|
+
async function executeQuery2(executor, input, context) {
|
|
5230
5309
|
const startTime = Date.now();
|
|
5231
5310
|
logger8.debug("Executing query", {
|
|
5232
5311
|
vendor: input.vendor,
|
|
@@ -5235,10 +5314,13 @@ async function executeQuery2(manager, input, context) {
|
|
|
5235
5314
|
});
|
|
5236
5315
|
try {
|
|
5237
5316
|
validateSqlString(input.sql, input.vendor);
|
|
5317
|
+
if (context?.transaction) {
|
|
5318
|
+
validateManagedTransactionSql(input.sql, input.vendor);
|
|
5319
|
+
}
|
|
5238
5320
|
enforceParameterizedQueryUsage(input.sql, input.params, input.vendor);
|
|
5239
5321
|
const parameterizedQuery = buildParameterizedQuery(input.sql, input.params);
|
|
5240
|
-
const
|
|
5241
|
-
const result = await
|
|
5322
|
+
const session = context?.transaction ?? executor;
|
|
5323
|
+
const result = await session.execute(parameterizedQuery);
|
|
5242
5324
|
const executionTime = Date.now() - startTime;
|
|
5243
5325
|
const rows = Array.isArray(result) ? result : result.rows || [];
|
|
5244
5326
|
const rowCount = result.rowCount || result.affectedRows || rows.length;
|
|
@@ -5261,7 +5343,7 @@ async function executeQuery2(manager, input, context) {
|
|
|
5261
5343
|
});
|
|
5262
5344
|
if (error instanceof Error) {
|
|
5263
5345
|
const message = error.message;
|
|
5264
|
-
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")) {
|
|
5346
|
+
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")) {
|
|
5265
5347
|
throw error;
|
|
5266
5348
|
}
|
|
5267
5349
|
}
|
|
@@ -6118,6 +6200,8 @@ var ManagedTransaction = class {
|
|
|
6118
6200
|
executeQuery;
|
|
6119
6201
|
completed = false;
|
|
6120
6202
|
cancelledReason = null;
|
|
6203
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
6204
|
+
inFlightFailure = null;
|
|
6121
6205
|
savepointCounter = 0;
|
|
6122
6206
|
sqliteReadUncommittedOriginal = null;
|
|
6123
6207
|
shouldRestoreSqliteReadUncommitted = false;
|
|
@@ -6130,14 +6214,36 @@ var ManagedTransaction = class {
|
|
|
6130
6214
|
isActive() {
|
|
6131
6215
|
return !this.completed;
|
|
6132
6216
|
}
|
|
6133
|
-
|
|
6217
|
+
execute(query) {
|
|
6218
|
+
try {
|
|
6219
|
+
return this.executeScopedQuery(query);
|
|
6220
|
+
} catch (error) {
|
|
6221
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
async settle() {
|
|
6225
|
+
while (this.inFlight.size > 0) {
|
|
6226
|
+
await Promise.allSettled([...this.inFlight]);
|
|
6227
|
+
}
|
|
6228
|
+
return this.inFlightFailure;
|
|
6229
|
+
}
|
|
6230
|
+
executeScopedQuery(query) {
|
|
6134
6231
|
if (this.cancelledReason) {
|
|
6135
6232
|
throw new Error(this.cancelledReason);
|
|
6136
6233
|
}
|
|
6137
6234
|
if (!this.isActive()) {
|
|
6138
6235
|
throw new Error("Transaction is no longer active");
|
|
6139
6236
|
}
|
|
6140
|
-
|
|
6237
|
+
const execution = this.executeQuery(query);
|
|
6238
|
+
this.inFlight.add(execution);
|
|
6239
|
+
void execution.then(
|
|
6240
|
+
() => this.inFlight.delete(execution),
|
|
6241
|
+
(reason) => {
|
|
6242
|
+
this.inFlightFailure ??= { reason };
|
|
6243
|
+
this.inFlight.delete(execution);
|
|
6244
|
+
}
|
|
6245
|
+
);
|
|
6246
|
+
return execution;
|
|
6141
6247
|
}
|
|
6142
6248
|
async begin() {
|
|
6143
6249
|
if (this.vendor === "mysql") {
|
|
@@ -6174,7 +6280,7 @@ var ManagedTransaction = class {
|
|
|
6174
6280
|
throw new Error("Transaction is no longer active");
|
|
6175
6281
|
}
|
|
6176
6282
|
const savepointName = this.normalizeSavepointName(name ?? `sp_${++this.savepointCounter}`);
|
|
6177
|
-
await this.
|
|
6283
|
+
await this.executeScopedQuery(sql.raw(`SAVEPOINT ${savepointName}`));
|
|
6178
6284
|
return savepointName;
|
|
6179
6285
|
}
|
|
6180
6286
|
async rollbackToSavepoint(name) {
|
|
@@ -6182,14 +6288,14 @@ var ManagedTransaction = class {
|
|
|
6182
6288
|
throw new Error("Transaction is no longer active");
|
|
6183
6289
|
}
|
|
6184
6290
|
const savepointName = this.normalizeSavepointName(name);
|
|
6185
|
-
await this.
|
|
6291
|
+
await this.executeScopedQuery(sql.raw(`ROLLBACK TO SAVEPOINT ${savepointName}`));
|
|
6186
6292
|
}
|
|
6187
6293
|
async releaseSavepoint(name) {
|
|
6188
6294
|
if (!this.isActive()) {
|
|
6189
6295
|
throw new Error("Transaction is no longer active");
|
|
6190
6296
|
}
|
|
6191
6297
|
const savepointName = this.normalizeSavepointName(name);
|
|
6192
|
-
await this.
|
|
6298
|
+
await this.executeScopedQuery(sql.raw(`RELEASE SAVEPOINT ${savepointName}`));
|
|
6193
6299
|
}
|
|
6194
6300
|
async withSavepoint(operation, name) {
|
|
6195
6301
|
const savepointName = await this.createSavepoint(name);
|
|
@@ -6288,17 +6394,24 @@ async function withTransaction(manager, operation, options) {
|
|
|
6288
6394
|
executeQuery: executeQuery3,
|
|
6289
6395
|
options: resolvedOptions
|
|
6290
6396
|
});
|
|
6291
|
-
|
|
6397
|
+
let timedOut = false;
|
|
6292
6398
|
try {
|
|
6399
|
+
await transaction.begin();
|
|
6293
6400
|
const result = await withTransactionTimeout(
|
|
6294
6401
|
operation(transaction),
|
|
6295
6402
|
resolvedOptions.timeoutMs,
|
|
6296
6403
|
() => {
|
|
6297
6404
|
if (resolvedOptions.timeoutMs) {
|
|
6405
|
+
timedOut = true;
|
|
6298
6406
|
transaction.cancel(`Transaction timed out after ${resolvedOptions.timeoutMs}ms`);
|
|
6299
6407
|
}
|
|
6300
6408
|
}
|
|
6301
6409
|
);
|
|
6410
|
+
transaction.cancel("Transaction is no longer active");
|
|
6411
|
+
const inFlightFailure = await transaction.settle();
|
|
6412
|
+
if (inFlightFailure) {
|
|
6413
|
+
throw inFlightFailure.reason;
|
|
6414
|
+
}
|
|
6302
6415
|
if (transaction.isActive()) {
|
|
6303
6416
|
await transaction.commit();
|
|
6304
6417
|
}
|
|
@@ -6309,6 +6422,10 @@ async function withTransaction(manager, operation, options) {
|
|
|
6309
6422
|
});
|
|
6310
6423
|
return result;
|
|
6311
6424
|
} catch (error) {
|
|
6425
|
+
if (!timedOut) {
|
|
6426
|
+
transaction.cancel(error instanceof Error ? error.message : "Transaction failed");
|
|
6427
|
+
}
|
|
6428
|
+
await transaction.settle();
|
|
6312
6429
|
if (transaction.isActive()) {
|
|
6313
6430
|
try {
|
|
6314
6431
|
await transaction.rollback();
|
|
@@ -6813,16 +6930,33 @@ async function inspectSQLite(runQueryRows) {
|
|
|
6813
6930
|
// src/data/relational/schema/schema-inspector.ts
|
|
6814
6931
|
var logger11 = createLogger("agentforge:tools:data:relational:schema-inspector");
|
|
6815
6932
|
var DEFAULT_CACHE_TTL_MS = 6e4;
|
|
6816
|
-
var
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
this.
|
|
6933
|
+
var SchemaCache = class {
|
|
6934
|
+
entries = /* @__PURE__ */ new Map();
|
|
6935
|
+
get(cacheKey) {
|
|
6936
|
+
return this.entries.get(cacheKey);
|
|
6937
|
+
}
|
|
6938
|
+
set(cacheKey, entry) {
|
|
6939
|
+
this.entries.set(cacheKey, entry);
|
|
6940
|
+
}
|
|
6941
|
+
delete(cacheKey) {
|
|
6942
|
+
this.entries.delete(cacheKey);
|
|
6943
|
+
}
|
|
6944
|
+
clear() {
|
|
6945
|
+
this.entries.clear();
|
|
6946
|
+
}
|
|
6947
|
+
};
|
|
6948
|
+
var schemaCache = new SchemaCache();
|
|
6949
|
+
var SchemaInspector = class {
|
|
6950
|
+
constructor(executor, vendor, config) {
|
|
6951
|
+
this.executor = executor;
|
|
6820
6952
|
this.vendor = vendor;
|
|
6821
6953
|
this.cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
6822
6954
|
this.cacheKey = config?.cacheKey;
|
|
6955
|
+
this.cache = config?.cache ?? schemaCache;
|
|
6823
6956
|
}
|
|
6824
6957
|
cacheTtlMs;
|
|
6825
6958
|
cacheKey;
|
|
6959
|
+
cache;
|
|
6826
6960
|
static clearCache(cacheKey) {
|
|
6827
6961
|
if (cacheKey) {
|
|
6828
6962
|
schemaCache.delete(cacheKey);
|
|
@@ -6831,21 +6965,25 @@ var SchemaInspector = class _SchemaInspector {
|
|
|
6831
6965
|
schemaCache.clear();
|
|
6832
6966
|
}
|
|
6833
6967
|
invalidateCache() {
|
|
6834
|
-
|
|
6968
|
+
if (this.cacheKey) {
|
|
6969
|
+
this.cache.delete(this.cacheKey);
|
|
6970
|
+
return;
|
|
6971
|
+
}
|
|
6972
|
+
this.cache.clear();
|
|
6835
6973
|
}
|
|
6836
6974
|
async inspect(options) {
|
|
6837
6975
|
const tableFilters = validateTableFilters(options?.tables);
|
|
6838
6976
|
const bypassCache = options?.bypassCache ?? false;
|
|
6839
|
-
if (!bypassCache && this.cacheKey) {
|
|
6840
|
-
const cached =
|
|
6977
|
+
if (!bypassCache && this.cacheKey && this.cacheTtlMs > 0) {
|
|
6978
|
+
const cached = this.cache.get(this.cacheKey);
|
|
6841
6979
|
if (cached && cached.expiresAt > Date.now()) {
|
|
6842
6980
|
logger11.debug("Schema cache hit", { vendor: this.vendor });
|
|
6843
6981
|
return filterSchemaTables(cloneSchema(cached.schema), tableFilters);
|
|
6844
6982
|
}
|
|
6845
6983
|
}
|
|
6846
6984
|
const schema = await this.inspectFromDatabase();
|
|
6847
|
-
if (this.cacheKey && this.cacheTtlMs > 0) {
|
|
6848
|
-
|
|
6985
|
+
if (!bypassCache && this.cacheKey && this.cacheTtlMs > 0) {
|
|
6986
|
+
this.cache.set(this.cacheKey, {
|
|
6849
6987
|
schema: cloneSchema(schema),
|
|
6850
6988
|
expiresAt: Date.now() + this.cacheTtlMs
|
|
6851
6989
|
});
|
|
@@ -6877,7 +7015,7 @@ var SchemaInspector = class _SchemaInspector {
|
|
|
6877
7015
|
}
|
|
6878
7016
|
}
|
|
6879
7017
|
async runQueryRows(query) {
|
|
6880
|
-
const result = await executeQuery2(this.
|
|
7018
|
+
const result = await executeQuery2(this.executor, {
|
|
6881
7019
|
sql: query,
|
|
6882
7020
|
vendor: this.vendor
|
|
6883
7021
|
});
|
|
@@ -7336,95 +7474,29 @@ function sortedReplacer(_key, value) {
|
|
|
7336
7474
|
}
|
|
7337
7475
|
return value;
|
|
7338
7476
|
}
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
7347
|
-
});
|
|
7348
|
-
var relationalQuery = 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(ToolCategory.DATABASE).tags(["database", "sql", "query", "postgresql", "mysql", "sqlite"]).schema(relationalQuerySchema).example({
|
|
7349
|
-
description: "SELECT query with positional parameters",
|
|
7350
|
-
input: {
|
|
7351
|
-
sql: "SELECT * FROM users WHERE id = $1",
|
|
7352
|
-
params: [42],
|
|
7353
|
-
vendor: "postgresql",
|
|
7354
|
-
connectionString: "postgresql://user:pass@localhost:5432/mydb"
|
|
7355
|
-
}
|
|
7356
|
-
}).example({
|
|
7357
|
-
description: "INSERT query with named parameters",
|
|
7358
|
-
input: {
|
|
7359
|
-
sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
|
|
7360
|
-
params: { name: "John Doe", email: "john@example.com" },
|
|
7361
|
-
vendor: "postgresql",
|
|
7362
|
-
connectionString: "postgresql://user:pass@localhost:5432/mydb"
|
|
7363
|
-
}
|
|
7364
|
-
}).example({
|
|
7365
|
-
description: "UPDATE query with positional parameters",
|
|
7366
|
-
input: {
|
|
7367
|
-
sql: "UPDATE users SET status = ? WHERE id = ?",
|
|
7368
|
-
params: ["active", 42],
|
|
7369
|
-
vendor: "mysql",
|
|
7370
|
-
connectionString: "mysql://user:pass@localhost:3306/mydb"
|
|
7371
|
-
}
|
|
7372
|
-
}).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) => {
|
|
7373
|
-
const manager = new ConnectionManager({
|
|
7374
|
-
vendor: input.vendor,
|
|
7375
|
-
connection: input.connectionString || ""
|
|
7376
|
-
});
|
|
7377
|
-
try {
|
|
7378
|
-
await manager.initialize();
|
|
7379
|
-
const result = await executeQuery2(manager, {
|
|
7380
|
-
sql: input.sql,
|
|
7381
|
-
params: input.params,
|
|
7382
|
-
vendor: input.vendor
|
|
7383
|
-
});
|
|
7384
|
-
return {
|
|
7385
|
-
success: true,
|
|
7386
|
-
rows: result.rows,
|
|
7387
|
-
rowCount: result.rowCount,
|
|
7388
|
-
executionTime: result.executionTime
|
|
7389
|
-
};
|
|
7390
|
-
} catch (error) {
|
|
7391
|
-
return {
|
|
7392
|
-
success: false,
|
|
7393
|
-
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
7394
|
-
rows: [],
|
|
7395
|
-
rowCount: 0
|
|
7396
|
-
};
|
|
7397
|
-
} finally {
|
|
7398
|
-
await manager.close();
|
|
7399
|
-
}
|
|
7400
|
-
}).build();
|
|
7401
|
-
var whereOperatorSchema = z.enum([
|
|
7477
|
+
|
|
7478
|
+
// src/data/relational/connection-failure-messages.ts
|
|
7479
|
+
var QUERY_CONNECTION_FAILURE = "Failed to connect to the configured database.";
|
|
7480
|
+
var SELECT_CONNECTION_FAILURE = "Failed to execute SELECT query. Please verify the configured database connection.";
|
|
7481
|
+
var INSERT_CONNECTION_FAILURE = "Failed to execute INSERT query. Please verify the configured database connection.";
|
|
7482
|
+
var UPDATE_CONNECTION_FAILURE = "Failed to execute UPDATE query. Please verify the configured database connection.";
|
|
7483
|
+
var DELETE_CONNECTION_FAILURE = "Failed to execute DELETE query. Please verify the configured database connection.";
|
|
7484
|
+
var deleteWhereOperatorSchema = z.enum([
|
|
7402
7485
|
"eq",
|
|
7403
|
-
// equals
|
|
7404
7486
|
"ne",
|
|
7405
|
-
// not equals
|
|
7406
7487
|
"gt",
|
|
7407
|
-
// greater than
|
|
7408
7488
|
"lt",
|
|
7409
|
-
// less than
|
|
7410
7489
|
"gte",
|
|
7411
|
-
// greater than or equal
|
|
7412
7490
|
"lte",
|
|
7413
|
-
// less than or equal
|
|
7414
7491
|
"like",
|
|
7415
|
-
// LIKE pattern matching
|
|
7416
7492
|
"in",
|
|
7417
|
-
// IN array
|
|
7418
7493
|
"notIn",
|
|
7419
|
-
// NOT IN array
|
|
7420
7494
|
"isNull",
|
|
7421
|
-
// IS NULL
|
|
7422
7495
|
"isNotNull"
|
|
7423
|
-
// IS NOT NULL
|
|
7424
7496
|
]);
|
|
7425
|
-
var
|
|
7497
|
+
var deleteWhereConditionSchema = z.object({
|
|
7426
7498
|
column: z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
|
|
7427
|
-
operator:
|
|
7499
|
+
operator: deleteWhereOperatorSchema.describe("Comparison operator"),
|
|
7428
7500
|
value: z.union([
|
|
7429
7501
|
z.string(),
|
|
7430
7502
|
z.number(),
|
|
@@ -7432,10 +7504,10 @@ var whereConditionSchema = z.object({
|
|
|
7432
7504
|
z.array(z.union([z.string(), z.number()])),
|
|
7433
7505
|
z.null()
|
|
7434
7506
|
]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
|
|
7435
|
-
}).superRefine((
|
|
7436
|
-
const op =
|
|
7507
|
+
}).superRefine((value, ctx) => {
|
|
7508
|
+
const op = value.operator;
|
|
7437
7509
|
if (op === "isNull" || op === "isNotNull") {
|
|
7438
|
-
if (
|
|
7510
|
+
if (value.value !== void 0) {
|
|
7439
7511
|
ctx.addIssue({
|
|
7440
7512
|
code: z.ZodIssueCode.custom,
|
|
7441
7513
|
path: ["value"],
|
|
@@ -7444,7 +7516,7 @@ var whereConditionSchema = z.object({
|
|
|
7444
7516
|
}
|
|
7445
7517
|
return;
|
|
7446
7518
|
}
|
|
7447
|
-
if (
|
|
7519
|
+
if (value.value === void 0) {
|
|
7448
7520
|
ctx.addIssue({
|
|
7449
7521
|
code: z.ZodIssueCode.custom,
|
|
7450
7522
|
path: ["value"],
|
|
@@ -7453,13 +7525,13 @@ var whereConditionSchema = z.object({
|
|
|
7453
7525
|
return;
|
|
7454
7526
|
}
|
|
7455
7527
|
if (op === "in" || op === "notIn") {
|
|
7456
|
-
if (!Array.isArray(
|
|
7528
|
+
if (!Array.isArray(value.value)) {
|
|
7457
7529
|
ctx.addIssue({
|
|
7458
7530
|
code: z.ZodIssueCode.custom,
|
|
7459
7531
|
path: ["value"],
|
|
7460
7532
|
message: `${op === "in" ? "IN" : "NOT IN"} operator requires an array value`
|
|
7461
7533
|
});
|
|
7462
|
-
} else if (
|
|
7534
|
+
} else if (value.value.length === 0) {
|
|
7463
7535
|
ctx.addIssue({
|
|
7464
7536
|
code: z.ZodIssueCode.custom,
|
|
7465
7537
|
path: ["value"],
|
|
@@ -7468,7 +7540,7 @@ var whereConditionSchema = z.object({
|
|
|
7468
7540
|
}
|
|
7469
7541
|
return;
|
|
7470
7542
|
}
|
|
7471
|
-
if (
|
|
7543
|
+
if (value.value === null) {
|
|
7472
7544
|
ctx.addIssue({
|
|
7473
7545
|
code: z.ZodIssueCode.custom,
|
|
7474
7546
|
path: ["value"],
|
|
@@ -7476,7 +7548,7 @@ var whereConditionSchema = z.object({
|
|
|
7476
7548
|
});
|
|
7477
7549
|
return;
|
|
7478
7550
|
}
|
|
7479
|
-
if (op === "like" && typeof
|
|
7551
|
+
if (op === "like" && typeof value.value !== "string") {
|
|
7480
7552
|
ctx.addIssue({
|
|
7481
7553
|
code: z.ZodIssueCode.custom,
|
|
7482
7554
|
path: ["value"],
|
|
@@ -7484,326 +7556,124 @@ var whereConditionSchema = z.object({
|
|
|
7484
7556
|
});
|
|
7485
7557
|
return;
|
|
7486
7558
|
}
|
|
7487
|
-
if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof
|
|
7559
|
+
if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof value.value !== "string" && typeof value.value !== "number") {
|
|
7488
7560
|
ctx.addIssue({
|
|
7489
7561
|
code: z.ZodIssueCode.custom,
|
|
7490
7562
|
path: ["value"],
|
|
7491
7563
|
message: `${op.toUpperCase()} operator requires a string or number value`
|
|
7492
7564
|
});
|
|
7493
|
-
return;
|
|
7494
7565
|
}
|
|
7495
|
-
|
|
7566
|
+
});
|
|
7567
|
+
var deleteSoftDeleteSchema = z.object({
|
|
7568
|
+
column: z.string().min(1, "Soft delete column must not be empty").default("deleted_at").describe("Column to write deletion timestamp/value into"),
|
|
7569
|
+
value: z.union([z.string(), z.number()]).optional().describe("Optional explicit value for the soft-delete column (defaults to ISO timestamp)")
|
|
7570
|
+
}).describe("Enable soft delete by updating a column instead of physically deleting rows");
|
|
7571
|
+
var deleteBatchOperationSchema = z.object({
|
|
7572
|
+
where: z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
7573
|
+
allowFullTableDelete: z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions for this operation"),
|
|
7574
|
+
cascade: z.boolean().default(false).describe("Enable cascade-aware error messaging for this operation"),
|
|
7575
|
+
softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE for this operation")
|
|
7576
|
+
}).superRefine((value, ctx) => {
|
|
7577
|
+
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
7578
|
+
if (!value.allowFullTableDelete && !hasWhere) {
|
|
7496
7579
|
ctx.addIssue({
|
|
7497
7580
|
code: z.ZodIssueCode.custom,
|
|
7498
|
-
path: ["
|
|
7499
|
-
message:
|
|
7581
|
+
path: ["where"],
|
|
7582
|
+
message: "WHERE conditions are required unless allowFullTableDelete is true"
|
|
7500
7583
|
});
|
|
7501
7584
|
}
|
|
7502
7585
|
});
|
|
7503
|
-
var
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
chunkSize: z.number().int().min(1).max(5e3).optional().describe("Rows fetched per chunk (default: 100)"),
|
|
7511
|
-
maxRows: z.number().int().positive().optional().describe("Optional cap on streamed rows"),
|
|
7512
|
-
sampleSize: z.number().int().min(0).max(5e3).optional().describe("Number of rows to include in the response payload"),
|
|
7513
|
-
benchmark: 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)")
|
|
7586
|
+
var deleteBatchOptionsSchema = z.object({
|
|
7587
|
+
enabled: z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
|
|
7588
|
+
batchSize: z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
|
|
7589
|
+
continueOnError: z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
|
|
7590
|
+
maxRetries: z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
|
|
7591
|
+
retryDelayMs: z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
|
|
7592
|
+
benchmark: z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
|
|
7514
7593
|
});
|
|
7515
|
-
var
|
|
7594
|
+
var relationalDeleteSchema = z.object({
|
|
7516
7595
|
table: z.string().min(1, "Table name is required").regex(
|
|
7517
7596
|
VALID_QUALIFIED_IDENTIFIER_PATTERN,
|
|
7518
7597
|
"Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
|
|
7519
|
-
).describe("Table name to
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7598
|
+
).describe("Table name to delete from (schema-qualified names supported, e.g. public.users)"),
|
|
7599
|
+
where: z.array(deleteWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
7600
|
+
allowFullTableDelete: z.boolean().default(false).describe("Explicitly allow DELETE without WHERE conditions"),
|
|
7601
|
+
cascade: z.boolean().default(false).describe("Enable cascade-aware error messaging for foreign key constraints"),
|
|
7602
|
+
softDelete: deleteSoftDeleteSchema.optional().describe("Use soft-delete semantics instead of hard DELETE"),
|
|
7603
|
+
operations: z.array(deleteBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch delete operation list"),
|
|
7604
|
+
batch: deleteBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
|
|
7526
7605
|
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
7527
7606
|
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string")
|
|
7607
|
+
}).superRefine((value, ctx) => {
|
|
7608
|
+
const hasOperations = (value.operations?.length ?? 0) > 0;
|
|
7609
|
+
if (hasOperations && (value.where?.length ?? 0) > 0) {
|
|
7610
|
+
ctx.addIssue({
|
|
7611
|
+
code: z.ZodIssueCode.custom,
|
|
7612
|
+
path: ["where"],
|
|
7613
|
+
message: "where cannot be provided when operations[] is used"
|
|
7614
|
+
});
|
|
7615
|
+
}
|
|
7616
|
+
if (hasOperations && value.allowFullTableDelete) {
|
|
7617
|
+
ctx.addIssue({
|
|
7618
|
+
code: z.ZodIssueCode.custom,
|
|
7619
|
+
path: ["allowFullTableDelete"],
|
|
7620
|
+
message: "allowFullTableDelete cannot be provided when operations[] is used"
|
|
7621
|
+
});
|
|
7622
|
+
}
|
|
7623
|
+
if (hasOperations && value.cascade) {
|
|
7624
|
+
ctx.addIssue({
|
|
7625
|
+
code: z.ZodIssueCode.custom,
|
|
7626
|
+
path: ["cascade"],
|
|
7627
|
+
message: "cascade cannot be provided when operations[] is used"
|
|
7628
|
+
});
|
|
7629
|
+
}
|
|
7630
|
+
if (hasOperations && value.softDelete) {
|
|
7631
|
+
ctx.addIssue({
|
|
7632
|
+
code: z.ZodIssueCode.custom,
|
|
7633
|
+
path: ["softDelete"],
|
|
7634
|
+
message: "softDelete cannot be provided when operations[] is used"
|
|
7635
|
+
});
|
|
7636
|
+
}
|
|
7637
|
+
if (hasOperations) {
|
|
7638
|
+
return;
|
|
7639
|
+
}
|
|
7640
|
+
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
7641
|
+
if (!value.allowFullTableDelete && !hasWhere) {
|
|
7642
|
+
ctx.addIssue({
|
|
7643
|
+
code: z.ZodIssueCode.custom,
|
|
7644
|
+
path: ["where"],
|
|
7645
|
+
message: "WHERE conditions are required unless allowFullTableDelete is true"
|
|
7646
|
+
});
|
|
7647
|
+
}
|
|
7528
7648
|
});
|
|
7529
7649
|
|
|
7530
|
-
// src/data/relational/tools/relational-
|
|
7531
|
-
|
|
7532
|
-
const queryInput = {
|
|
7533
|
-
table: input.table,
|
|
7534
|
-
columns: input.columns,
|
|
7535
|
-
where: input.where,
|
|
7536
|
-
orderBy: input.orderBy,
|
|
7537
|
-
limit: input.limit,
|
|
7538
|
-
offset: input.offset,
|
|
7539
|
-
vendor: input.vendor
|
|
7540
|
-
};
|
|
7541
|
-
return buildSelectQuery(queryInput);
|
|
7542
|
-
}
|
|
7543
|
-
|
|
7544
|
-
// src/data/relational/tools/relational-select/error-utils.ts
|
|
7545
|
-
var SAFE_VALIDATION_ERROR_PATTERNS = [
|
|
7650
|
+
// src/data/relational/tools/relational-delete/error-utils.ts
|
|
7651
|
+
var SAFE_DELETE_VALIDATION_PATTERNS = [
|
|
7546
7652
|
"must not be empty",
|
|
7547
7653
|
"contains invalid characters",
|
|
7548
|
-
"
|
|
7549
|
-
"
|
|
7550
|
-
"
|
|
7551
|
-
"must be a positive integer",
|
|
7654
|
+
"WHERE conditions are required for DELETE queries",
|
|
7655
|
+
"WHERE conditions are required unless allowFullTableDelete is true",
|
|
7656
|
+
"value is required for this operator",
|
|
7552
7657
|
"null is only allowed with isNull/isNotNull operators",
|
|
7553
|
-
"
|
|
7658
|
+
"operator requires a value",
|
|
7554
7659
|
"operator requires a string or number value",
|
|
7555
|
-
"operator requires a
|
|
7556
|
-
"
|
|
7557
|
-
|
|
7558
|
-
function isSafeValidationError(error) {
|
|
7559
|
-
if (!(error instanceof Error)) {
|
|
7560
|
-
return false;
|
|
7561
|
-
}
|
|
7562
|
-
return SAFE_VALIDATION_ERROR_PATTERNS.some(
|
|
7563
|
-
(pattern) => error.message.includes(pattern)
|
|
7564
|
-
);
|
|
7565
|
-
}
|
|
7566
|
-
|
|
7567
|
-
// src/data/relational/tools/relational-select/executor.ts
|
|
7568
|
-
var logger16 = createLogger("agentforge:tools:data:relational:select");
|
|
7569
|
-
function toSelectQueryInput(input) {
|
|
7570
|
-
return {
|
|
7571
|
-
table: input.table,
|
|
7572
|
-
columns: input.columns,
|
|
7573
|
-
where: input.where,
|
|
7574
|
-
orderBy: input.orderBy,
|
|
7575
|
-
limit: input.limit,
|
|
7576
|
-
offset: input.offset,
|
|
7577
|
-
vendor: input.vendor
|
|
7578
|
-
};
|
|
7579
|
-
}
|
|
7580
|
-
async function executeSelect(manager, input, context) {
|
|
7581
|
-
const startTime = Date.now();
|
|
7582
|
-
logger16.debug("Building SELECT query", {
|
|
7583
|
-
vendor: input.vendor,
|
|
7584
|
-
table: input.table,
|
|
7585
|
-
hasWhere: !!input.where,
|
|
7586
|
-
hasOrderBy: !!input.orderBy,
|
|
7587
|
-
hasLimit: !!input.limit,
|
|
7588
|
-
streamingEnabled: !!input.streaming?.enabled
|
|
7589
|
-
});
|
|
7590
|
-
try {
|
|
7591
|
-
const executor = context?.transaction ?? manager;
|
|
7592
|
-
if (input.streaming?.enabled) {
|
|
7593
|
-
const streamOptions = {
|
|
7594
|
-
chunkSize: input.streaming.chunkSize,
|
|
7595
|
-
maxRows: input.streaming.maxRows,
|
|
7596
|
-
sampleSize: input.streaming.sampleSize
|
|
7597
|
-
};
|
|
7598
|
-
const streamInput = toSelectQueryInput(input);
|
|
7599
|
-
if (input.streaming.benchmark) {
|
|
7600
|
-
logger16.warn("Streaming benchmark enabled; SELECT will execute up to three times (result + benchmark regular + benchmark streaming).", {
|
|
7601
|
-
vendor: input.vendor,
|
|
7602
|
-
table: input.table,
|
|
7603
|
-
chunkSize: streamOptions.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
7604
|
-
...streamOptions.maxRows !== void 0 ? { maxRows: streamOptions.maxRows } : {},
|
|
7605
|
-
...streamOptions.sampleSize !== void 0 ? { sampleSize: streamOptions.sampleSize } : {}
|
|
7606
|
-
});
|
|
7607
|
-
}
|
|
7608
|
-
const streamResult = await executeStreamingSelect(executor, streamInput, streamOptions);
|
|
7609
|
-
const benchmark = input.streaming.benchmark ? await benchmarkStreamingSelectMemory(executor, streamInput, streamOptions) : void 0;
|
|
7610
|
-
const executionTime2 = Date.now() - startTime;
|
|
7611
|
-
logger16.debug("Streaming SELECT query executed successfully", {
|
|
7612
|
-
vendor: input.vendor,
|
|
7613
|
-
table: input.table,
|
|
7614
|
-
rowCount: streamResult.rowCount,
|
|
7615
|
-
chunkCount: streamResult.chunkCount,
|
|
7616
|
-
executionTime: executionTime2,
|
|
7617
|
-
cancelled: streamResult.cancelled
|
|
7618
|
-
});
|
|
7619
|
-
return {
|
|
7620
|
-
rows: streamResult.rows,
|
|
7621
|
-
rowCount: streamResult.rowCount,
|
|
7622
|
-
executionTime: executionTime2,
|
|
7623
|
-
streaming: {
|
|
7624
|
-
enabled: true,
|
|
7625
|
-
chunkSize: input.streaming.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
7626
|
-
chunkCount: streamResult.chunkCount,
|
|
7627
|
-
sampledRowCount: streamResult.rows.length,
|
|
7628
|
-
streamedRowCount: streamResult.rowCount,
|
|
7629
|
-
cancelled: streamResult.cancelled,
|
|
7630
|
-
memoryUsage: streamResult.memoryUsage,
|
|
7631
|
-
benchmark
|
|
7632
|
-
}
|
|
7633
|
-
};
|
|
7634
|
-
}
|
|
7635
|
-
const query = buildSelectQuery2(input);
|
|
7636
|
-
const result = await executor.execute(query);
|
|
7637
|
-
const executionTime = Date.now() - startTime;
|
|
7638
|
-
const rows = Array.isArray(result) ? result : result.rows || [];
|
|
7639
|
-
const rowCount = rows.length;
|
|
7640
|
-
logger16.debug("SELECT query executed successfully", {
|
|
7641
|
-
vendor: input.vendor,
|
|
7642
|
-
table: input.table,
|
|
7643
|
-
rowCount,
|
|
7644
|
-
executionTime
|
|
7645
|
-
});
|
|
7646
|
-
return {
|
|
7647
|
-
rows,
|
|
7648
|
-
rowCount,
|
|
7649
|
-
executionTime
|
|
7650
|
-
};
|
|
7651
|
-
} catch (error) {
|
|
7652
|
-
const executionTime = Date.now() - startTime;
|
|
7653
|
-
logger16.error("SELECT query execution failed", {
|
|
7654
|
-
vendor: input.vendor,
|
|
7655
|
-
table: input.table,
|
|
7656
|
-
error: error instanceof Error ? error.message : String(error),
|
|
7657
|
-
executionTime
|
|
7658
|
-
});
|
|
7659
|
-
if (isSafeValidationError(error)) {
|
|
7660
|
-
throw error;
|
|
7661
|
-
}
|
|
7662
|
-
throw new Error("SELECT query failed. See logs for details.", { cause: error });
|
|
7663
|
-
}
|
|
7664
|
-
}
|
|
7665
|
-
|
|
7666
|
-
// src/data/relational/tools/relational-select/index.ts
|
|
7667
|
-
var relationalSelect = 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(ToolCategory.DATABASE).tags(["database", "sql", "select", "query", "postgresql", "mysql", "sqlite"]).schema(relationalSelectSchema).example({
|
|
7668
|
-
description: "SELECT with WHERE condition",
|
|
7669
|
-
input: {
|
|
7670
|
-
table: "users",
|
|
7671
|
-
columns: ["id", "name", "email"],
|
|
7672
|
-
where: [{ column: "status", operator: "eq", value: "active" }],
|
|
7673
|
-
vendor: "postgresql",
|
|
7674
|
-
connectionString: "postgresql://localhost/mydb"
|
|
7675
|
-
}
|
|
7676
|
-
}).example({
|
|
7677
|
-
description: "SELECT with pagination",
|
|
7678
|
-
input: {
|
|
7679
|
-
table: "products",
|
|
7680
|
-
orderBy: [{ column: "created_at", direction: "desc" }],
|
|
7681
|
-
limit: 20,
|
|
7682
|
-
offset: 0,
|
|
7683
|
-
vendor: "mysql",
|
|
7684
|
-
connectionString: "mysql://localhost/shop"
|
|
7685
|
-
}
|
|
7686
|
-
}).example({
|
|
7687
|
-
description: "SELECT with streaming mode",
|
|
7688
|
-
input: {
|
|
7689
|
-
table: "events",
|
|
7690
|
-
orderBy: [{ column: "id", direction: "asc" }],
|
|
7691
|
-
streaming: {
|
|
7692
|
-
enabled: true,
|
|
7693
|
-
chunkSize: 250,
|
|
7694
|
-
sampleSize: 25
|
|
7695
|
-
},
|
|
7696
|
-
vendor: "postgresql",
|
|
7697
|
-
connectionString: "postgresql://localhost/analytics"
|
|
7698
|
-
}
|
|
7699
|
-
}).implement(async (input) => {
|
|
7700
|
-
const manager = new ConnectionManager({
|
|
7701
|
-
vendor: input.vendor,
|
|
7702
|
-
connection: input.connectionString
|
|
7703
|
-
});
|
|
7704
|
-
try {
|
|
7705
|
-
await manager.connect();
|
|
7706
|
-
const result = await executeSelect(manager, input);
|
|
7707
|
-
return {
|
|
7708
|
-
success: true,
|
|
7709
|
-
rows: result.rows,
|
|
7710
|
-
rowCount: result.rowCount,
|
|
7711
|
-
executionTime: result.executionTime,
|
|
7712
|
-
streaming: result.streaming
|
|
7713
|
-
};
|
|
7714
|
-
} catch (error) {
|
|
7715
|
-
let errorMessage = "Failed to execute SELECT query. Please verify your input and database connection.";
|
|
7716
|
-
if (isSafeValidationError(error)) {
|
|
7717
|
-
errorMessage = error.message;
|
|
7718
|
-
}
|
|
7719
|
-
return {
|
|
7720
|
-
success: false,
|
|
7721
|
-
error: errorMessage,
|
|
7722
|
-
rows: [],
|
|
7723
|
-
rowCount: 0
|
|
7724
|
-
};
|
|
7725
|
-
} finally {
|
|
7726
|
-
await manager.disconnect();
|
|
7727
|
-
}
|
|
7728
|
-
}).build();
|
|
7729
|
-
var insertValueSchema = z.union([
|
|
7730
|
-
z.string().describe("String value"),
|
|
7731
|
-
z.number().describe("Numeric value"),
|
|
7732
|
-
z.boolean().describe("Boolean value"),
|
|
7733
|
-
z.null().describe("Null value")
|
|
7734
|
-
]).describe("Scalar value for an INSERT column");
|
|
7735
|
-
var insertRowSchema = z.record(
|
|
7736
|
-
z.string().min(1, "Column name must not be empty").describe("Column name"),
|
|
7737
|
-
insertValueSchema
|
|
7738
|
-
).describe("One row object where keys are column names and values are scalar values");
|
|
7739
|
-
var insertReturningModeSchema = z.enum(["none", "id", "row"]);
|
|
7740
|
-
var insertReturningSchema = z.object({
|
|
7741
|
-
mode: insertReturningModeSchema.default("none").describe("Returning mode: none, id, or full row"),
|
|
7742
|
-
idColumn: z.string().min(1, "idColumn must not be empty").optional().describe('Primary key column to return when mode is "id"')
|
|
7743
|
-
}).superRefine((value, ctx) => {
|
|
7744
|
-
if (value.idColumn && value.mode !== "id") {
|
|
7745
|
-
ctx.addIssue({
|
|
7746
|
-
code: z.ZodIssueCode.custom,
|
|
7747
|
-
path: ["idColumn"],
|
|
7748
|
-
message: 'idColumn can only be provided when returning.mode is "id"'
|
|
7749
|
-
});
|
|
7750
|
-
}
|
|
7751
|
-
});
|
|
7752
|
-
var insertBatchOptionsSchema = z.object({
|
|
7753
|
-
enabled: z.boolean().optional().default(true).describe("Enable chunked batch execution for array insert payloads"),
|
|
7754
|
-
batchSize: z.number().int().positive().max(5e3).optional().default(100).describe("Rows per batch chunk"),
|
|
7755
|
-
continueOnError: z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
|
|
7756
|
-
maxRetries: z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
|
|
7757
|
-
retryDelayMs: z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
|
|
7758
|
-
benchmark: z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
|
|
7759
|
-
});
|
|
7760
|
-
var relationalInsertSchema = z.object({
|
|
7761
|
-
table: z.string().min(1, "Table name is required").regex(
|
|
7762
|
-
VALID_QUALIFIED_IDENTIFIER_PATTERN,
|
|
7763
|
-
"Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
|
|
7764
|
-
).describe("Table name to insert into (schema-qualified names supported, e.g. public.users)"),
|
|
7765
|
-
data: z.union([
|
|
7766
|
-
insertRowSchema,
|
|
7767
|
-
z.array(insertRowSchema).min(1, "Insert data array must not be empty")
|
|
7768
|
-
]).describe("Single row object or array of row objects to insert"),
|
|
7769
|
-
batch: insertBatchOptionsSchema.optional().describe("Optional batch execution controls for array insert payloads"),
|
|
7770
|
-
returning: insertReturningSchema.optional().describe("Optional RETURNING behavior"),
|
|
7771
|
-
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
7772
|
-
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string")
|
|
7773
|
-
});
|
|
7774
|
-
|
|
7775
|
-
// src/data/relational/tools/relational-insert/error-utils.ts
|
|
7776
|
-
var SAFE_INSERT_VALIDATION_PATTERNS = [
|
|
7777
|
-
"must not be empty",
|
|
7778
|
-
"contains invalid characters",
|
|
7779
|
-
"must be an object",
|
|
7780
|
-
"must not be an empty array",
|
|
7781
|
-
"must not be undefined",
|
|
7782
|
-
"idColumn can only be provided",
|
|
7783
|
-
"Returning full rows is not supported for mysql",
|
|
7784
|
-
"Batch INSERT with only DEFAULT VALUES is not supported"
|
|
7660
|
+
"operator requires a string value",
|
|
7661
|
+
"operator requires a non-empty array value",
|
|
7662
|
+
"must not include value"
|
|
7785
7663
|
];
|
|
7786
7664
|
var CONSTRAINT_VIOLATION_PATTERNS = [
|
|
7787
7665
|
{
|
|
7788
|
-
pattern: /(
|
|
7789
|
-
message: "
|
|
7790
|
-
},
|
|
7791
|
-
{
|
|
7792
|
-
pattern: /(foreign key constraint|violates foreign key constraint)/i,
|
|
7793
|
-
message: "Insert failed: foreign key constraint violation."
|
|
7794
|
-
},
|
|
7795
|
-
{
|
|
7796
|
-
pattern: /(not null constraint|cannot be null)/i,
|
|
7797
|
-
message: "Insert failed: NOT NULL constraint violation."
|
|
7666
|
+
pattern: /(foreign key constraint|violates foreign key constraint|foreign key mismatch|a foreign key constraint fails)/i,
|
|
7667
|
+
message: "Delete failed: foreign key constraint violation."
|
|
7798
7668
|
}
|
|
7799
7669
|
];
|
|
7800
|
-
function
|
|
7670
|
+
function isSafeDeleteValidationError(error) {
|
|
7801
7671
|
if (!(error instanceof Error)) {
|
|
7802
7672
|
return false;
|
|
7803
7673
|
}
|
|
7804
|
-
return
|
|
7674
|
+
return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
7805
7675
|
}
|
|
7806
|
-
function
|
|
7676
|
+
function getDeleteConstraintViolationMessage(error, cascade) {
|
|
7807
7677
|
if (!(error instanceof Error)) {
|
|
7808
7678
|
return null;
|
|
7809
7679
|
}
|
|
@@ -7813,79 +7683,43 @@ function getConstraintViolationMessage(error) {
|
|
|
7813
7683
|
}
|
|
7814
7684
|
for (const entry of CONSTRAINT_VIOLATION_PATTERNS) {
|
|
7815
7685
|
if (messages.some((msg) => entry.pattern.test(msg))) {
|
|
7686
|
+
if (cascade) {
|
|
7687
|
+
return `${entry.message} Verify database-level ON DELETE CASCADE is configured for related foreign keys.`;
|
|
7688
|
+
}
|
|
7816
7689
|
return entry.message;
|
|
7817
7690
|
}
|
|
7818
7691
|
}
|
|
7819
7692
|
return null;
|
|
7820
7693
|
}
|
|
7821
|
-
function
|
|
7694
|
+
function isSafeDeleteError(error) {
|
|
7822
7695
|
if (!(error instanceof Error)) {
|
|
7823
7696
|
return false;
|
|
7824
7697
|
}
|
|
7825
|
-
return
|
|
7698
|
+
return SAFE_DELETE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern)) || error.message.startsWith("Delete failed:");
|
|
7826
7699
|
}
|
|
7827
|
-
var
|
|
7700
|
+
var deleteExecutorLogger = createLogger("agentforge:tools:data:relational:delete");
|
|
7828
7701
|
function toNumber(value) {
|
|
7829
7702
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7830
7703
|
}
|
|
7831
|
-
function
|
|
7832
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7833
|
-
}
|
|
7834
|
-
function normalizeExecutionResult(result) {
|
|
7704
|
+
function normalizeAffectedRows(result) {
|
|
7835
7705
|
if (Array.isArray(result)) {
|
|
7836
|
-
if (result.length > 0 &&
|
|
7706
|
+
if (result.length > 0 && result[0] && typeof result[0] === "object") {
|
|
7837
7707
|
const first = result[0];
|
|
7838
7708
|
const affectedRows = toNumber(first.affectedRows) ?? toNumber(first.rowCount) ?? toNumber(first.changes);
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
if (affectedRows !== void 0 || insertId !== void 0 || lastInsertRowid !== void 0) {
|
|
7842
|
-
return {
|
|
7843
|
-
rows: [],
|
|
7844
|
-
rowCount: affectedRows ?? 0,
|
|
7845
|
-
insertId,
|
|
7846
|
-
lastInsertRowid
|
|
7847
|
-
};
|
|
7709
|
+
if (affectedRows !== void 0) {
|
|
7710
|
+
return affectedRows;
|
|
7848
7711
|
}
|
|
7849
7712
|
}
|
|
7850
|
-
return
|
|
7851
|
-
rows: result,
|
|
7852
|
-
rowCount: result.length
|
|
7853
|
-
};
|
|
7713
|
+
return result.length;
|
|
7854
7714
|
}
|
|
7855
|
-
if (
|
|
7856
|
-
const
|
|
7857
|
-
|
|
7858
|
-
return {
|
|
7859
|
-
rows,
|
|
7860
|
-
rowCount,
|
|
7861
|
-
insertId: toNumber(result.insertId),
|
|
7862
|
-
lastInsertRowid: toNumber(result.lastInsertRowid)
|
|
7863
|
-
};
|
|
7715
|
+
if (result && typeof result === "object") {
|
|
7716
|
+
const resultRecord = result;
|
|
7717
|
+
return toNumber(resultRecord.rowCount) ?? toNumber(resultRecord.affectedRows) ?? toNumber(resultRecord.changes) ?? (Array.isArray(resultRecord.rows) ? resultRecord.rows.length : 0);
|
|
7864
7718
|
}
|
|
7865
|
-
return
|
|
7866
|
-
rows: [],
|
|
7867
|
-
rowCount: 0
|
|
7868
|
-
};
|
|
7719
|
+
return 0;
|
|
7869
7720
|
}
|
|
7870
|
-
function
|
|
7871
|
-
|
|
7872
|
-
if (returnedRows.length > 0) {
|
|
7873
|
-
const idsFromReturn = returnedRows.map((row) => isPlainObject(row) ? row[idColumn] : void 0).filter((id) => typeof id === "number" || typeof id === "string");
|
|
7874
|
-
if (idsFromReturn.length > 0) {
|
|
7875
|
-
return idsFromReturn;
|
|
7876
|
-
}
|
|
7877
|
-
}
|
|
7878
|
-
if (inputRows.every((row) => typeof row[idColumn] === "number" || typeof row[idColumn] === "string")) {
|
|
7879
|
-
return inputRows.map((row) => row[idColumn]);
|
|
7880
|
-
}
|
|
7881
|
-
if (insertId !== void 0 && rowCount > 0) {
|
|
7882
|
-
return Array.from({ length: rowCount }, (_, index) => insertId + index);
|
|
7883
|
-
}
|
|
7884
|
-
if (lastInsertRowid !== void 0 && rowCount > 0) {
|
|
7885
|
-
const startId = lastInsertRowid - rowCount + 1;
|
|
7886
|
-
return Array.from({ length: rowCount }, (_, index) => startId + index);
|
|
7887
|
-
}
|
|
7888
|
-
return [];
|
|
7721
|
+
function toErrorMessage(error) {
|
|
7722
|
+
return error instanceof Error ? error.message : String(error);
|
|
7889
7723
|
}
|
|
7890
7724
|
function resolveBatchOptions(batch) {
|
|
7891
7725
|
if (!batch || batch.enabled === false) {
|
|
@@ -7900,79 +7734,87 @@ function resolveBatchOptions(batch) {
|
|
|
7900
7734
|
benchmark: batch.benchmark ?? false
|
|
7901
7735
|
};
|
|
7902
7736
|
}
|
|
7903
|
-
function
|
|
7904
|
-
return
|
|
7737
|
+
function toSingleDeleteOperation(input) {
|
|
7738
|
+
return {
|
|
7739
|
+
where: input.where,
|
|
7740
|
+
allowFullTableDelete: input.allowFullTableDelete,
|
|
7741
|
+
cascade: input.cascade,
|
|
7742
|
+
softDelete: input.softDelete
|
|
7743
|
+
};
|
|
7905
7744
|
}
|
|
7906
7745
|
|
|
7907
|
-
// src/data/relational/tools/relational-
|
|
7908
|
-
async function
|
|
7909
|
-
const built =
|
|
7746
|
+
// src/data/relational/tools/relational-delete/executor-single.ts
|
|
7747
|
+
async function executeSingleDelete(executor, input, operation, context) {
|
|
7748
|
+
const built = buildDeleteQuery({
|
|
7910
7749
|
table: input.table,
|
|
7911
|
-
|
|
7912
|
-
|
|
7750
|
+
where: operation.where,
|
|
7751
|
+
allowFullTableDelete: operation.allowFullTableDelete,
|
|
7752
|
+
softDelete: operation.softDelete,
|
|
7913
7753
|
vendor: input.vendor
|
|
7914
7754
|
});
|
|
7915
|
-
const
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
const allRows = [];
|
|
7919
|
-
const allIds = [];
|
|
7920
|
-
for (const singleQuery of built.query) {
|
|
7921
|
-
const rawResult2 = await executor.execute(singleQuery);
|
|
7922
|
-
const normalized2 = normalizeExecutionResult(rawResult2);
|
|
7923
|
-
totalRowCount += normalized2.rowCount > 0 ? normalized2.rowCount : 1;
|
|
7924
|
-
if (built.returningMode === "row") {
|
|
7925
|
-
allRows.push(...normalized2.rows);
|
|
7926
|
-
}
|
|
7927
|
-
if (built.returningMode === "id") {
|
|
7928
|
-
const ids = deriveInsertedIds({
|
|
7929
|
-
idColumn: built.idColumn,
|
|
7930
|
-
inputRows: toSingleInsertRows(built.rows[allIds.length]),
|
|
7931
|
-
returnedRows: normalized2.rows,
|
|
7932
|
-
rowCount: 1,
|
|
7933
|
-
insertId: normalized2.insertId,
|
|
7934
|
-
lastInsertRowid: normalized2.lastInsertRowid
|
|
7935
|
-
});
|
|
7936
|
-
allIds.push(...ids);
|
|
7937
|
-
}
|
|
7938
|
-
}
|
|
7939
|
-
return {
|
|
7940
|
-
rowCount: totalRowCount,
|
|
7941
|
-
rows: allRows,
|
|
7942
|
-
insertedIds: allIds,
|
|
7943
|
-
executionTime: 0
|
|
7944
|
-
};
|
|
7945
|
-
}
|
|
7946
|
-
const rawResult = await executor.execute(built.query);
|
|
7947
|
-
const normalized = normalizeExecutionResult(rawResult);
|
|
7948
|
-
const rowCount = normalized.rowCount > 0 ? normalized.rowCount : built.rows.length;
|
|
7949
|
-
const rows = built.returningMode === "row" ? normalized.rows : [];
|
|
7950
|
-
const insertedIds = built.returningMode === "id" ? deriveInsertedIds({
|
|
7951
|
-
idColumn: built.idColumn,
|
|
7952
|
-
inputRows: built.rows,
|
|
7953
|
-
returnedRows: normalized.rows,
|
|
7954
|
-
rowCount,
|
|
7955
|
-
insertId: normalized.insertId,
|
|
7956
|
-
lastInsertRowid: normalized.lastInsertRowid
|
|
7957
|
-
}) : [];
|
|
7755
|
+
const session = context?.transaction ?? executor;
|
|
7756
|
+
const rawResult = await session.execute(built.query);
|
|
7757
|
+
const rowCount = normalizeAffectedRows(rawResult);
|
|
7958
7758
|
return {
|
|
7959
7759
|
rowCount,
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
executionTime: 0
|
|
7760
|
+
executionTime: 0,
|
|
7761
|
+
softDeleted: built.usesSoftDelete
|
|
7963
7762
|
};
|
|
7964
7763
|
}
|
|
7965
7764
|
|
|
7966
|
-
// src/data/relational/tools/relational-
|
|
7967
|
-
async function
|
|
7765
|
+
// src/data/relational/tools/relational-delete/executor-batch.ts
|
|
7766
|
+
async function executeDeleteInBatchMode(executor, input, context, options) {
|
|
7968
7767
|
const batchResult = await executeBatchedTask(
|
|
7969
7768
|
{
|
|
7970
|
-
operation: "
|
|
7971
|
-
items: input.
|
|
7972
|
-
executeBatch: async (
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7769
|
+
operation: "delete",
|
|
7770
|
+
items: input.operations,
|
|
7771
|
+
executeBatch: async (operations, batchIndex) => {
|
|
7772
|
+
let rowCount2 = 0;
|
|
7773
|
+
let successfulItems = 0;
|
|
7774
|
+
let failedItems = 0;
|
|
7775
|
+
let softDeletedCount = 0;
|
|
7776
|
+
const failures = [];
|
|
7777
|
+
for (const operation of operations) {
|
|
7778
|
+
try {
|
|
7779
|
+
const result = await executeSingleDelete(
|
|
7780
|
+
executor,
|
|
7781
|
+
input,
|
|
7782
|
+
{
|
|
7783
|
+
where: operation.where,
|
|
7784
|
+
allowFullTableDelete: operation.allowFullTableDelete,
|
|
7785
|
+
cascade: operation.cascade,
|
|
7786
|
+
softDelete: operation.softDelete
|
|
7787
|
+
},
|
|
7788
|
+
context
|
|
7789
|
+
);
|
|
7790
|
+
rowCount2 += result.rowCount;
|
|
7791
|
+
successfulItems += 1;
|
|
7792
|
+
if (result.softDeleted) {
|
|
7793
|
+
softDeletedCount += 1;
|
|
7794
|
+
}
|
|
7795
|
+
} catch (error) {
|
|
7796
|
+
if (!options.continueOnError) {
|
|
7797
|
+
throw error;
|
|
7798
|
+
}
|
|
7799
|
+
failedItems += 1;
|
|
7800
|
+
failures.push({
|
|
7801
|
+
operation: "delete",
|
|
7802
|
+
batchIndex,
|
|
7803
|
+
batchSize: operations.length,
|
|
7804
|
+
attempts: 1,
|
|
7805
|
+
error: toErrorMessage(error)
|
|
7806
|
+
});
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
return {
|
|
7810
|
+
rowCount: rowCount2,
|
|
7811
|
+
successfulItems,
|
|
7812
|
+
failedItems,
|
|
7813
|
+
softDeletedCount,
|
|
7814
|
+
failures
|
|
7815
|
+
};
|
|
7816
|
+
},
|
|
7817
|
+
getBatchSuccessCount: (result) => result.successfulItems
|
|
7976
7818
|
},
|
|
7977
7819
|
{
|
|
7978
7820
|
batchSize: options.batchSize,
|
|
@@ -7980,7 +7822,7 @@ async function executeInsertInBatchMode(manager, input, context, options) {
|
|
|
7980
7822
|
maxRetries: options.maxRetries,
|
|
7981
7823
|
retryDelayMs: options.retryDelayMs,
|
|
7982
7824
|
onProgress: (progress) => {
|
|
7983
|
-
|
|
7825
|
+
deleteExecutorLogger.debug("DELETE batch progress", {
|
|
7984
7826
|
vendor: input.vendor,
|
|
7985
7827
|
table: input.table,
|
|
7986
7828
|
...progress
|
|
@@ -7989,18 +7831,21 @@ async function executeInsertInBatchMode(manager, input, context, options) {
|
|
|
7989
7831
|
}
|
|
7990
7832
|
);
|
|
7991
7833
|
const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
|
|
7992
|
-
const
|
|
7993
|
-
const
|
|
7834
|
+
const softDeleted = batchResult.results.some((result) => result.softDeletedCount > 0);
|
|
7835
|
+
const operationFailures = batchResult.results.flatMap((result) => result.failures);
|
|
7994
7836
|
let benchmark;
|
|
7995
7837
|
if (options.benchmark) {
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
7838
|
+
deleteExecutorLogger.warn(
|
|
7839
|
+
"DELETE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.",
|
|
7840
|
+
{
|
|
7841
|
+
vendor: input.vendor,
|
|
7842
|
+
table: input.table,
|
|
7843
|
+
operationCount: input.operations.length,
|
|
7844
|
+
batchSize: options.batchSize
|
|
7845
|
+
}
|
|
7846
|
+
);
|
|
8002
7847
|
benchmark = await benchmarkBatchExecution({
|
|
8003
|
-
items: input.
|
|
7848
|
+
items: input.operations,
|
|
8004
7849
|
batchSize: options.batchSize,
|
|
8005
7850
|
runIndividual: async () => {
|
|
8006
7851
|
},
|
|
@@ -8010,9 +7855,8 @@ async function executeInsertInBatchMode(manager, input, context, options) {
|
|
|
8010
7855
|
}
|
|
8011
7856
|
return {
|
|
8012
7857
|
rowCount,
|
|
8013
|
-
rows,
|
|
8014
|
-
insertedIds,
|
|
8015
7858
|
executionTime: batchResult.executionTime,
|
|
7859
|
+
softDeleted,
|
|
8016
7860
|
batch: {
|
|
8017
7861
|
enabled: true,
|
|
8018
7862
|
batchSize: options.batchSize,
|
|
@@ -8023,38 +7867,41 @@ async function executeInsertInBatchMode(manager, input, context, options) {
|
|
|
8023
7867
|
totalBatches: batchResult.totalBatches,
|
|
8024
7868
|
retries: batchResult.retries,
|
|
8025
7869
|
partialSuccess: batchResult.partialSuccess,
|
|
8026
|
-
failures: batchResult.failures,
|
|
7870
|
+
failures: [...batchResult.failures, ...operationFailures],
|
|
8027
7871
|
benchmark
|
|
8028
7872
|
}
|
|
8029
7873
|
};
|
|
8030
7874
|
}
|
|
8031
7875
|
|
|
8032
|
-
// src/data/relational/tools/relational-
|
|
8033
|
-
async function
|
|
7876
|
+
// src/data/relational/tools/relational-delete/executor.ts
|
|
7877
|
+
async function executeDelete(executor, input, context) {
|
|
8034
7878
|
const startTime = Date.now();
|
|
8035
|
-
|
|
7879
|
+
deleteExecutorLogger.debug("Building DELETE query", {
|
|
8036
7880
|
vendor: input.vendor,
|
|
8037
7881
|
table: input.table,
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
7882
|
+
hasWhere: !!input.where?.length,
|
|
7883
|
+
...input.allowFullTableDelete !== void 0 ? { allowFullTableDelete: input.allowFullTableDelete } : {},
|
|
7884
|
+
...input.cascade !== void 0 ? { cascade: input.cascade } : {},
|
|
7885
|
+
softDelete: !!input.softDelete,
|
|
7886
|
+
operationCount: input.operations?.length ?? 0,
|
|
7887
|
+
batchModeEnabled: !!input.batch?.enabled
|
|
8041
7888
|
});
|
|
8042
7889
|
try {
|
|
8043
7890
|
const batchOptions = resolveBatchOptions(input.batch);
|
|
8044
|
-
const result =
|
|
8045
|
-
|
|
7891
|
+
const result = input.operations && batchOptions ? await executeDeleteInBatchMode(
|
|
7892
|
+
executor,
|
|
8046
7893
|
input,
|
|
8047
7894
|
context,
|
|
8048
7895
|
batchOptions
|
|
8049
|
-
) : await
|
|
7896
|
+
) : await executeSingleDelete(executor, input, toSingleDeleteOperation(input), context);
|
|
8050
7897
|
const executionTime = Date.now() - startTime;
|
|
8051
|
-
|
|
7898
|
+
deleteExecutorLogger.debug("DELETE query executed successfully", {
|
|
8052
7899
|
vendor: input.vendor,
|
|
8053
7900
|
table: input.table,
|
|
8054
7901
|
rowCount: result.rowCount,
|
|
8055
|
-
returnedRows: result.rows.length,
|
|
8056
|
-
returnedIds: result.insertedIds.length,
|
|
8057
7902
|
executionTime,
|
|
7903
|
+
softDelete: result.softDeleted,
|
|
7904
|
+
...input.cascade !== void 0 ? { cascade: input.cascade } : {},
|
|
8058
7905
|
batchMode: !!result.batch,
|
|
8059
7906
|
partialSuccess: result.batch?.partialSuccess ?? false
|
|
8060
7907
|
});
|
|
@@ -8064,314 +7911,332 @@ async function executeInsert(manager, input, context) {
|
|
|
8064
7911
|
};
|
|
8065
7912
|
} catch (error) {
|
|
8066
7913
|
const executionTime = Date.now() - startTime;
|
|
8067
|
-
|
|
7914
|
+
deleteExecutorLogger.error("DELETE query execution failed", {
|
|
8068
7915
|
vendor: input.vendor,
|
|
8069
7916
|
table: input.table,
|
|
8070
7917
|
error: error instanceof Error ? error.message : String(error),
|
|
8071
|
-
executionTime
|
|
7918
|
+
executionTime,
|
|
7919
|
+
...input.cascade !== void 0 ? { cascade: input.cascade } : {},
|
|
7920
|
+
softDelete: !!input.softDelete
|
|
8072
7921
|
});
|
|
8073
|
-
const constraintMessage =
|
|
7922
|
+
const constraintMessage = getDeleteConstraintViolationMessage(error, input.cascade ?? false);
|
|
8074
7923
|
if (constraintMessage) {
|
|
8075
7924
|
throw new Error(constraintMessage, { cause: error });
|
|
8076
7925
|
}
|
|
8077
|
-
if (
|
|
7926
|
+
if (isSafeDeleteValidationError(error)) {
|
|
8078
7927
|
throw error;
|
|
8079
7928
|
}
|
|
8080
|
-
throw new Error("
|
|
7929
|
+
throw new Error("DELETE query failed. See logs for details.", { cause: error });
|
|
8081
7930
|
}
|
|
8082
7931
|
}
|
|
8083
7932
|
|
|
8084
|
-
// src/data/relational/tools/
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
table: "users",
|
|
8089
|
-
data: { name: "Alice", email: "alice@example.com" },
|
|
8090
|
-
returning: { mode: "id", idColumn: "id" },
|
|
8091
|
-
vendor: "postgresql",
|
|
8092
|
-
connectionString: "postgresql://localhost/mydb"
|
|
8093
|
-
}
|
|
8094
|
-
}).example({
|
|
8095
|
-
description: "Batch insert rows",
|
|
8096
|
-
input: {
|
|
8097
|
-
table: "users",
|
|
8098
|
-
data: [
|
|
8099
|
-
{ name: "Bob", email: "bob@example.com" },
|
|
8100
|
-
{ name: "Carol", email: "carol@example.com" }
|
|
8101
|
-
],
|
|
8102
|
-
batch: {
|
|
8103
|
-
batchSize: 250,
|
|
8104
|
-
continueOnError: true,
|
|
8105
|
-
maxRetries: 1
|
|
8106
|
-
},
|
|
8107
|
-
vendor: "sqlite",
|
|
8108
|
-
connectionString: "data.db"
|
|
7933
|
+
// src/data/relational/tools/legacy-mutation-adapter.ts
|
|
7934
|
+
function replaceMutationConnectionFailure(result, configuredMessage, legacyMessage) {
|
|
7935
|
+
if (!result.success && result.error === configuredMessage) {
|
|
7936
|
+
return { ...result, error: legacyMessage };
|
|
8109
7937
|
}
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
7938
|
+
return result;
|
|
7939
|
+
}
|
|
7940
|
+
async function withEphemeralRelationalMutationToolSet(database, invoke, toErrorResponse) {
|
|
7941
|
+
const toolSet = createLegacyRelationalMutationToolSet({
|
|
7942
|
+
vendor: database.vendor,
|
|
7943
|
+
connection: database.connectionString
|
|
8114
7944
|
});
|
|
8115
7945
|
try {
|
|
8116
|
-
|
|
8117
|
-
|
|
7946
|
+
try {
|
|
7947
|
+
return await invoke(toolSet);
|
|
7948
|
+
} catch (error) {
|
|
7949
|
+
if (error instanceof MissingPeerDependencyError) {
|
|
7950
|
+
throw error;
|
|
7951
|
+
}
|
|
7952
|
+
return toErrorResponse(error);
|
|
7953
|
+
}
|
|
7954
|
+
} finally {
|
|
7955
|
+
await toolSet.dispose();
|
|
7956
|
+
}
|
|
7957
|
+
}
|
|
7958
|
+
|
|
7959
|
+
// src/data/relational/tools/relational-delete/index.ts
|
|
7960
|
+
function toDeleteErrorResponse(error) {
|
|
7961
|
+
const errorMessage = isSafeDeleteError(error) ? error.message : "Failed to execute DELETE query. Please verify your input and database connection.";
|
|
7962
|
+
return {
|
|
7963
|
+
success: false,
|
|
7964
|
+
error: errorMessage,
|
|
7965
|
+
rowCount: 0,
|
|
7966
|
+
softDeleted: false
|
|
7967
|
+
};
|
|
7968
|
+
}
|
|
7969
|
+
async function invokeRelationalDelete(execution, input) {
|
|
7970
|
+
try {
|
|
7971
|
+
const result = await executeDelete(
|
|
7972
|
+
execution.executor,
|
|
7973
|
+
{ ...input, vendor: execution.vendor },
|
|
7974
|
+
execution.transaction ? { transaction: execution.transaction } : void 0
|
|
7975
|
+
);
|
|
8118
7976
|
return {
|
|
8119
7977
|
success: true,
|
|
8120
7978
|
rowCount: result.rowCount,
|
|
8121
|
-
insertedIds: result.insertedIds,
|
|
8122
|
-
rows: result.rows,
|
|
8123
7979
|
executionTime: result.executionTime,
|
|
7980
|
+
softDeleted: result.softDeleted,
|
|
8124
7981
|
batch: result.batch
|
|
8125
7982
|
};
|
|
8126
7983
|
} catch (error) {
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
7984
|
+
return toDeleteErrorResponse(error);
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
var relationalDelete = toolBuilder().name("relational-delete").displayName("Relational DELETE").description("Execute type-safe DELETE queries with single and batched operation support").category(ToolCategory.DATABASE).tags(["database", "sql", "delete", "postgresql", "mysql", "sqlite"]).schema(relationalDeleteSchema).example({
|
|
7988
|
+
description: "Delete one row by condition",
|
|
7989
|
+
input: {
|
|
7990
|
+
table: "users",
|
|
7991
|
+
where: [{ column: "id", operator: "eq", value: 123 }],
|
|
7992
|
+
vendor: "postgresql",
|
|
7993
|
+
connectionString: "postgresql://localhost/mydb"
|
|
7994
|
+
}
|
|
7995
|
+
}).example({
|
|
7996
|
+
description: "Soft delete rows",
|
|
7997
|
+
input: {
|
|
7998
|
+
table: "users",
|
|
7999
|
+
where: [{ column: "status", operator: "eq", value: "inactive" }],
|
|
8000
|
+
softDelete: { column: "deleted_at" },
|
|
8001
|
+
vendor: "sqlite",
|
|
8002
|
+
connectionString: "data.db"
|
|
8003
|
+
}
|
|
8004
|
+
}).example({
|
|
8005
|
+
description: "Batch delete operations",
|
|
8006
|
+
input: {
|
|
8007
|
+
table: "users",
|
|
8008
|
+
operations: [
|
|
8009
|
+
{
|
|
8010
|
+
where: [{ column: "status", operator: "eq", value: "inactive" }],
|
|
8011
|
+
softDelete: { column: "deleted_at" }
|
|
8012
|
+
},
|
|
8013
|
+
{
|
|
8014
|
+
where: [{ column: "status", operator: "eq", value: "pending_delete" }],
|
|
8015
|
+
softDelete: { column: "deleted_at" }
|
|
8016
|
+
}
|
|
8017
|
+
],
|
|
8018
|
+
batch: {
|
|
8019
|
+
batchSize: 100,
|
|
8020
|
+
continueOnError: true
|
|
8021
|
+
},
|
|
8022
|
+
vendor: "postgresql",
|
|
8023
|
+
connectionString: "postgresql://localhost/mydb"
|
|
8140
8024
|
}
|
|
8025
|
+
}).implement(async (input) => {
|
|
8026
|
+
const { connectionString, vendor, ...operation } = input;
|
|
8027
|
+
return withEphemeralRelationalMutationToolSet(
|
|
8028
|
+
{ vendor, connectionString },
|
|
8029
|
+
async (toolSet) => replaceMutationConnectionFailure(
|
|
8030
|
+
await toolSet.delete.invoke(operation),
|
|
8031
|
+
DELETE_CONNECTION_FAILURE,
|
|
8032
|
+
"Failed to execute DELETE query. Please verify your input and database connection."
|
|
8033
|
+
),
|
|
8034
|
+
toDeleteErrorResponse
|
|
8035
|
+
);
|
|
8141
8036
|
}).build();
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
if (Object.keys(value).length === 0) {
|
|
8153
|
-
ctx.addIssue({
|
|
8154
|
-
code: z.ZodIssueCode.custom,
|
|
8155
|
-
message: "Update data must not be empty"
|
|
8156
|
-
});
|
|
8037
|
+
|
|
8038
|
+
// src/data/relational/tools/relational-get-schema-error-utils.ts
|
|
8039
|
+
var SAFE_VALIDATION_ERROR_PATTERNS = [
|
|
8040
|
+
"must not be empty",
|
|
8041
|
+
"contains invalid characters",
|
|
8042
|
+
"Invalid table filter"
|
|
8043
|
+
];
|
|
8044
|
+
function isSafeGetSchemaValidationError(error) {
|
|
8045
|
+
if (!(error instanceof Error)) {
|
|
8046
|
+
return false;
|
|
8157
8047
|
}
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
"in",
|
|
8168
|
-
"notIn",
|
|
8169
|
-
"isNull",
|
|
8170
|
-
"isNotNull"
|
|
8171
|
-
]);
|
|
8172
|
-
var updateWhereConditionSchema = z.object({
|
|
8173
|
-
column: z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
|
|
8174
|
-
operator: updateWhereOperatorSchema.describe("Comparison operator"),
|
|
8175
|
-
value: z.union([
|
|
8176
|
-
z.string(),
|
|
8177
|
-
z.number(),
|
|
8178
|
-
z.boolean(),
|
|
8179
|
-
z.array(z.union([z.string(), z.number()])),
|
|
8180
|
-
z.null()
|
|
8181
|
-
]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
|
|
8182
|
-
}).superRefine((value, ctx) => {
|
|
8183
|
-
const op = value.operator;
|
|
8184
|
-
if (op === "isNull" || op === "isNotNull") {
|
|
8185
|
-
if (value.value !== void 0) {
|
|
8186
|
-
ctx.addIssue({
|
|
8187
|
-
code: z.ZodIssueCode.custom,
|
|
8188
|
-
path: ["value"],
|
|
8189
|
-
message: "value must not be provided when using isNull or isNotNull operator"
|
|
8190
|
-
});
|
|
8191
|
-
}
|
|
8192
|
-
return;
|
|
8048
|
+
return SAFE_VALIDATION_ERROR_PATTERNS.some(
|
|
8049
|
+
(pattern) => error.message.includes(pattern)
|
|
8050
|
+
);
|
|
8051
|
+
}
|
|
8052
|
+
|
|
8053
|
+
// src/data/relational/tools/legacy-read-adapter.ts
|
|
8054
|
+
function replaceConnectionFailureMessage(result, configuredMessage, legacyMessage) {
|
|
8055
|
+
if (!result.success && result.error === configuredMessage) {
|
|
8056
|
+
return { ...result, error: legacyMessage };
|
|
8193
8057
|
}
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
return;
|
|
8058
|
+
return result;
|
|
8059
|
+
}
|
|
8060
|
+
async function withEphemeralRelationalToolSet(database, invoke, options = {}, sharedSchemaCacheKey) {
|
|
8061
|
+
const config = { vendor: database.vendor, connection: database.connectionString };
|
|
8062
|
+
const toolSet = createLegacyRelationalReadToolSet(config, options, sharedSchemaCacheKey);
|
|
8063
|
+
try {
|
|
8064
|
+
return await invoke(toolSet);
|
|
8065
|
+
} finally {
|
|
8066
|
+
await toolSet.dispose();
|
|
8201
8067
|
}
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8068
|
+
}
|
|
8069
|
+
|
|
8070
|
+
// src/data/relational/tools/relational-get-schema.ts
|
|
8071
|
+
var logger16 = createLogger("agentforge:tools:data:relational:get-schema");
|
|
8072
|
+
function buildSchemaCacheKey(vendor, connectionString, database) {
|
|
8073
|
+
const databaseScope = database ?? "default";
|
|
8074
|
+
const connectionHash = createHash("sha256").update(connectionString).digest("hex");
|
|
8075
|
+
return `${vendor}:${databaseScope}:${connectionHash}`;
|
|
8076
|
+
}
|
|
8077
|
+
var relationalGetSchemaInputSchema = z.object({
|
|
8078
|
+
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
8079
|
+
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection"),
|
|
8080
|
+
database: z.string().min(1).optional().describe("Optional logical database name used for cache scoping"),
|
|
8081
|
+
tables: z.array(
|
|
8082
|
+
z.string().regex(
|
|
8083
|
+
VALID_TABLE_FILTER_PATTERN,
|
|
8084
|
+
"Table filter contains invalid characters. Use alphanumeric, underscore, and optional schema qualification."
|
|
8085
|
+
)
|
|
8086
|
+
).optional().describe("Optional table filter list. When omitted, all tables are returned."),
|
|
8087
|
+
cacheTtlMs: z.number().int().min(0).optional().describe("Schema cache TTL in milliseconds (0 disables caching)"),
|
|
8088
|
+
refreshCache: z.boolean().optional().describe("Force cache invalidation before introspecting schema")
|
|
8089
|
+
});
|
|
8090
|
+
function toGetSchemaErrorResponse(execution, input, error) {
|
|
8091
|
+
logger16.error("Schema introspection failed", {
|
|
8092
|
+
vendor: execution.vendor,
|
|
8093
|
+
hasTablesFilter: Array.isArray(input.tables),
|
|
8094
|
+
tablesFilterCount: input.tables?.length ?? 0,
|
|
8095
|
+
refreshCache: input.refreshCache ?? false,
|
|
8096
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8097
|
+
});
|
|
8098
|
+
const errorMessage = isSafeGetSchemaValidationError(error) ? error.message : "Failed to inspect schema. See logs for details.";
|
|
8099
|
+
return {
|
|
8100
|
+
success: false,
|
|
8101
|
+
error: errorMessage,
|
|
8102
|
+
schema: null
|
|
8103
|
+
};
|
|
8104
|
+
}
|
|
8105
|
+
async function invokeRelationalGetSchema(execution, input) {
|
|
8106
|
+
const cacheKey = execution.schemaCacheKey ?? (execution.schemaCache ? input.database ?? "default" : void 0);
|
|
8107
|
+
const inspector = new SchemaInspector(
|
|
8108
|
+
execution.transaction ?? execution.executor,
|
|
8109
|
+
execution.vendor,
|
|
8110
|
+
{
|
|
8111
|
+
cacheTtlMs: input.cacheTtlMs,
|
|
8112
|
+
cacheKey,
|
|
8113
|
+
cache: execution.schemaCache
|
|
8215
8114
|
}
|
|
8216
|
-
|
|
8115
|
+
);
|
|
8116
|
+
try {
|
|
8117
|
+
if (input.refreshCache && cacheKey) {
|
|
8118
|
+
inspector.invalidateCache();
|
|
8119
|
+
}
|
|
8120
|
+
const schema = await inspector.inspect(
|
|
8121
|
+
execution.transaction ? { tables: input.tables, bypassCache: true } : { tables: input.tables }
|
|
8122
|
+
);
|
|
8123
|
+
const summary = schema.tables.reduce(
|
|
8124
|
+
(accumulator, table) => {
|
|
8125
|
+
accumulator.tableCount += 1;
|
|
8126
|
+
accumulator.columnCount += table.columns.length;
|
|
8127
|
+
accumulator.foreignKeyCount += table.foreignKeys.length;
|
|
8128
|
+
accumulator.indexCount += table.indexes.length;
|
|
8129
|
+
return accumulator;
|
|
8130
|
+
},
|
|
8131
|
+
{ tableCount: 0, columnCount: 0, foreignKeyCount: 0, indexCount: 0 }
|
|
8132
|
+
);
|
|
8133
|
+
return { success: true, schema, summary };
|
|
8134
|
+
} catch (error) {
|
|
8135
|
+
return toGetSchemaErrorResponse(execution, input, error);
|
|
8217
8136
|
}
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
return;
|
|
8137
|
+
}
|
|
8138
|
+
var relationalGetSchema = 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(ToolCategory.DATABASE).tags(["database", "schema", "introspection", "postgresql", "mysql", "sqlite"]).schema(relationalGetSchemaInputSchema).example({
|
|
8139
|
+
description: "Inspect all SQLite tables",
|
|
8140
|
+
input: {
|
|
8141
|
+
vendor: "sqlite",
|
|
8142
|
+
connectionString: ":memory:"
|
|
8225
8143
|
}
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8144
|
+
}).example({
|
|
8145
|
+
description: "Inspect selected PostgreSQL tables with cache control",
|
|
8146
|
+
input: {
|
|
8147
|
+
vendor: "postgresql",
|
|
8148
|
+
connectionString: "postgresql://localhost/app",
|
|
8149
|
+
tables: ["public.users", "public.orders"],
|
|
8150
|
+
cacheTtlMs: 3e4,
|
|
8151
|
+
refreshCache: true
|
|
8233
8152
|
}
|
|
8234
|
-
|
|
8153
|
+
}).implement(async (input) => {
|
|
8154
|
+
const { connectionString, vendor, database, cacheTtlMs, tables, refreshCache } = input;
|
|
8155
|
+
return withEphemeralRelationalToolSet(
|
|
8156
|
+
{ vendor, connectionString },
|
|
8157
|
+
(toolSet) => toolSet.getSchema.invoke({ tables, refreshCache }),
|
|
8158
|
+
{ schemaCacheTtlMs: cacheTtlMs },
|
|
8159
|
+
buildSchemaCacheKey(vendor, connectionString, database)
|
|
8160
|
+
);
|
|
8161
|
+
}).build();
|
|
8162
|
+
var insertValueSchema = z.union([
|
|
8163
|
+
z.string().describe("String value"),
|
|
8164
|
+
z.number().describe("Numeric value"),
|
|
8165
|
+
z.boolean().describe("Boolean value"),
|
|
8166
|
+
z.null().describe("Null value")
|
|
8167
|
+
]).describe("Scalar value for an INSERT column");
|
|
8168
|
+
var insertRowSchema = z.record(
|
|
8169
|
+
z.string().min(1, "Column name must not be empty").describe("Column name"),
|
|
8170
|
+
insertValueSchema
|
|
8171
|
+
).describe("One row object where keys are column names and values are scalar values");
|
|
8172
|
+
var insertReturningModeSchema = z.enum(["none", "id", "row"]);
|
|
8173
|
+
var insertReturningSchema = z.object({
|
|
8174
|
+
mode: insertReturningModeSchema.default("none").describe("Returning mode: none, id, or full row"),
|
|
8175
|
+
idColumn: z.string().min(1, "idColumn must not be empty").optional().describe('Primary key column to return when mode is "id"')
|
|
8176
|
+
}).superRefine((value, ctx) => {
|
|
8177
|
+
if (value.idColumn && value.mode !== "id") {
|
|
8235
8178
|
ctx.addIssue({
|
|
8236
8179
|
code: z.ZodIssueCode.custom,
|
|
8237
|
-
path: ["
|
|
8238
|
-
message:
|
|
8180
|
+
path: ["idColumn"],
|
|
8181
|
+
message: 'idColumn can only be provided when returning.mode is "id"'
|
|
8239
8182
|
});
|
|
8240
8183
|
}
|
|
8241
8184
|
});
|
|
8242
|
-
var
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
z.string().describe("Expected string version value"),
|
|
8246
|
-
z.number().describe("Expected numeric version value")
|
|
8247
|
-
]).describe("Expected current value for the lock column")
|
|
8248
|
-
}).describe("Optional optimistic lock condition");
|
|
8249
|
-
var updateBatchOperationSchema = z.object({
|
|
8250
|
-
data: updateDataSchema.describe("Columns and values to update"),
|
|
8251
|
-
where: z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
8252
|
-
allowFullTableUpdate: z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions for this operation"),
|
|
8253
|
-
optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition for this operation")
|
|
8254
|
-
}).superRefine((value, ctx) => {
|
|
8255
|
-
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
8256
|
-
const hasOptimisticLock = !!value.optimisticLock;
|
|
8257
|
-
if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
|
|
8258
|
-
ctx.addIssue({
|
|
8259
|
-
code: z.ZodIssueCode.custom,
|
|
8260
|
-
path: ["where"],
|
|
8261
|
-
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8262
|
-
});
|
|
8263
|
-
}
|
|
8264
|
-
});
|
|
8265
|
-
var updateBatchOptionsSchema = z.object({
|
|
8266
|
-
enabled: z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
|
|
8267
|
-
batchSize: z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
|
|
8185
|
+
var insertBatchOptionsSchema = z.object({
|
|
8186
|
+
enabled: z.boolean().optional().default(true).describe("Enable chunked batch execution for array insert payloads"),
|
|
8187
|
+
batchSize: z.number().int().positive().max(5e3).optional().default(100).describe("Rows per batch chunk"),
|
|
8268
8188
|
continueOnError: z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
|
|
8269
8189
|
maxRetries: z.number().int().min(0).max(5).optional().default(0).describe("Retry attempts per failed chunk"),
|
|
8270
8190
|
retryDelayMs: z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
|
|
8271
8191
|
benchmark: z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
|
|
8272
8192
|
});
|
|
8273
|
-
var
|
|
8193
|
+
var relationalInsertSchema = z.object({
|
|
8274
8194
|
table: z.string().min(1, "Table name is required").regex(
|
|
8275
8195
|
VALID_QUALIFIED_IDENTIFIER_PATTERN,
|
|
8276
8196
|
"Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
|
|
8277
|
-
).describe("Table name to
|
|
8278
|
-
data:
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8197
|
+
).describe("Table name to insert into (schema-qualified names supported, e.g. public.users)"),
|
|
8198
|
+
data: z.union([
|
|
8199
|
+
insertRowSchema,
|
|
8200
|
+
z.array(insertRowSchema).min(1, "Insert data array must not be empty")
|
|
8201
|
+
]).describe("Single row object or array of row objects to insert"),
|
|
8202
|
+
batch: insertBatchOptionsSchema.optional().describe("Optional batch execution controls for array insert payloads"),
|
|
8203
|
+
returning: insertReturningSchema.optional().describe("Optional RETURNING behavior"),
|
|
8284
8204
|
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
8285
8205
|
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string")
|
|
8286
|
-
}).superRefine((value, ctx) => {
|
|
8287
|
-
const hasOperations = (value.operations?.length ?? 0) > 0;
|
|
8288
|
-
if (hasOperations && value.data) {
|
|
8289
|
-
ctx.addIssue({
|
|
8290
|
-
code: z.ZodIssueCode.custom,
|
|
8291
|
-
path: ["data"],
|
|
8292
|
-
message: "data cannot be provided when operations[] is used"
|
|
8293
|
-
});
|
|
8294
|
-
}
|
|
8295
|
-
if (!hasOperations && !value.data) {
|
|
8296
|
-
ctx.addIssue({
|
|
8297
|
-
code: z.ZodIssueCode.custom,
|
|
8298
|
-
path: ["data"],
|
|
8299
|
-
message: "data is required when operations[] is not provided"
|
|
8300
|
-
});
|
|
8301
|
-
}
|
|
8302
|
-
if (hasOperations && (value.where?.length ?? 0) > 0) {
|
|
8303
|
-
ctx.addIssue({
|
|
8304
|
-
code: z.ZodIssueCode.custom,
|
|
8305
|
-
path: ["where"],
|
|
8306
|
-
message: "where cannot be provided when operations[] is used"
|
|
8307
|
-
});
|
|
8308
|
-
}
|
|
8309
|
-
if (hasOperations && value.optimisticLock) {
|
|
8310
|
-
ctx.addIssue({
|
|
8311
|
-
code: z.ZodIssueCode.custom,
|
|
8312
|
-
path: ["optimisticLock"],
|
|
8313
|
-
message: "optimisticLock cannot be provided when operations[] is used"
|
|
8314
|
-
});
|
|
8315
|
-
}
|
|
8316
|
-
if (hasOperations && value.allowFullTableUpdate) {
|
|
8317
|
-
ctx.addIssue({
|
|
8318
|
-
code: z.ZodIssueCode.custom,
|
|
8319
|
-
path: ["allowFullTableUpdate"],
|
|
8320
|
-
message: "allowFullTableUpdate cannot be provided when operations[] is used"
|
|
8321
|
-
});
|
|
8322
|
-
}
|
|
8323
|
-
if (hasOperations) {
|
|
8324
|
-
return;
|
|
8325
|
-
}
|
|
8326
|
-
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
8327
|
-
const hasOptimisticLock = !!value.optimisticLock;
|
|
8328
|
-
if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
|
|
8329
|
-
ctx.addIssue({
|
|
8330
|
-
code: z.ZodIssueCode.custom,
|
|
8331
|
-
path: ["where"],
|
|
8332
|
-
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8333
|
-
});
|
|
8334
|
-
}
|
|
8335
8206
|
});
|
|
8336
8207
|
|
|
8337
|
-
// src/data/relational/tools/relational-
|
|
8338
|
-
var
|
|
8208
|
+
// src/data/relational/tools/relational-insert/error-utils.ts
|
|
8209
|
+
var SAFE_INSERT_VALIDATION_PATTERNS = [
|
|
8339
8210
|
"must not be empty",
|
|
8340
8211
|
"contains invalid characters",
|
|
8341
|
-
"
|
|
8342
|
-
"
|
|
8212
|
+
"must be an object",
|
|
8213
|
+
"must not be an empty array",
|
|
8343
8214
|
"must not be undefined",
|
|
8344
|
-
"
|
|
8345
|
-
"
|
|
8346
|
-
"
|
|
8347
|
-
"operator requires a non-empty array value",
|
|
8348
|
-
"operator requires a string value",
|
|
8349
|
-
"must not include value",
|
|
8350
|
-
"Optimistic lock expectedValue must not be empty",
|
|
8351
|
-
"optimistic lock check failed",
|
|
8352
|
-
"WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8215
|
+
"idColumn can only be provided",
|
|
8216
|
+
"Returning full rows is not supported for mysql",
|
|
8217
|
+
"Batch INSERT with only DEFAULT VALUES is not supported"
|
|
8353
8218
|
];
|
|
8354
8219
|
var CONSTRAINT_VIOLATION_PATTERNS2 = [
|
|
8355
8220
|
{
|
|
8356
8221
|
pattern: /(unique constraint|duplicate key|duplicate entry)/i,
|
|
8357
|
-
message: "
|
|
8222
|
+
message: "Insert failed: unique constraint violation."
|
|
8358
8223
|
},
|
|
8359
8224
|
{
|
|
8360
8225
|
pattern: /(foreign key constraint|violates foreign key constraint)/i,
|
|
8361
|
-
message: "
|
|
8226
|
+
message: "Insert failed: foreign key constraint violation."
|
|
8362
8227
|
},
|
|
8363
8228
|
{
|
|
8364
8229
|
pattern: /(not null constraint|cannot be null)/i,
|
|
8365
|
-
message: "
|
|
8230
|
+
message: "Insert failed: NOT NULL constraint violation."
|
|
8366
8231
|
}
|
|
8367
8232
|
];
|
|
8368
|
-
function
|
|
8233
|
+
function isSafeInsertValidationError(error) {
|
|
8369
8234
|
if (!(error instanceof Error)) {
|
|
8370
8235
|
return false;
|
|
8371
8236
|
}
|
|
8372
|
-
return
|
|
8237
|
+
return SAFE_INSERT_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
8373
8238
|
}
|
|
8374
|
-
function
|
|
8239
|
+
function getConstraintViolationMessage(error) {
|
|
8375
8240
|
if (!(error instanceof Error)) {
|
|
8376
8241
|
return null;
|
|
8377
8242
|
}
|
|
@@ -8386,35 +8251,74 @@ function getUpdateConstraintViolationMessage(error) {
|
|
|
8386
8251
|
}
|
|
8387
8252
|
return null;
|
|
8388
8253
|
}
|
|
8389
|
-
function
|
|
8254
|
+
function isSafeInsertError(error) {
|
|
8390
8255
|
if (!(error instanceof Error)) {
|
|
8391
8256
|
return false;
|
|
8392
8257
|
}
|
|
8393
|
-
return
|
|
8258
|
+
return isSafeInsertValidationError(error) || getConstraintViolationMessage(error) !== null;
|
|
8394
8259
|
}
|
|
8395
|
-
var
|
|
8260
|
+
var insertExecutorLogger = createLogger("agentforge:tools:data:relational:insert");
|
|
8396
8261
|
function toNumber2(value) {
|
|
8397
8262
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8398
8263
|
}
|
|
8399
|
-
function
|
|
8264
|
+
function isPlainObject(value) {
|
|
8265
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8266
|
+
}
|
|
8267
|
+
function normalizeExecutionResult(result) {
|
|
8400
8268
|
if (Array.isArray(result)) {
|
|
8401
|
-
if (result.length > 0 && result[0]
|
|
8269
|
+
if (result.length > 0 && isPlainObject(result[0])) {
|
|
8402
8270
|
const first = result[0];
|
|
8403
8271
|
const affectedRows = toNumber2(first.affectedRows) ?? toNumber2(first.rowCount) ?? toNumber2(first.changes);
|
|
8404
|
-
|
|
8405
|
-
|
|
8272
|
+
const insertId = toNumber2(first.insertId);
|
|
8273
|
+
const lastInsertRowid = toNumber2(first.lastInsertRowid);
|
|
8274
|
+
if (affectedRows !== void 0 || insertId !== void 0 || lastInsertRowid !== void 0) {
|
|
8275
|
+
return {
|
|
8276
|
+
rows: [],
|
|
8277
|
+
rowCount: affectedRows ?? 0,
|
|
8278
|
+
insertId,
|
|
8279
|
+
lastInsertRowid
|
|
8280
|
+
};
|
|
8406
8281
|
}
|
|
8407
8282
|
}
|
|
8408
|
-
return
|
|
8283
|
+
return {
|
|
8284
|
+
rows: result,
|
|
8285
|
+
rowCount: result.length
|
|
8286
|
+
};
|
|
8409
8287
|
}
|
|
8410
|
-
if (result
|
|
8411
|
-
const
|
|
8412
|
-
|
|
8288
|
+
if (isPlainObject(result)) {
|
|
8289
|
+
const rows = Array.isArray(result.rows) ? result.rows : [];
|
|
8290
|
+
const rowCount = toNumber2(result.rowCount) ?? toNumber2(result.affectedRows) ?? toNumber2(result.changes) ?? rows.length;
|
|
8291
|
+
return {
|
|
8292
|
+
rows,
|
|
8293
|
+
rowCount,
|
|
8294
|
+
insertId: toNumber2(result.insertId),
|
|
8295
|
+
lastInsertRowid: toNumber2(result.lastInsertRowid)
|
|
8296
|
+
};
|
|
8413
8297
|
}
|
|
8414
|
-
return
|
|
8298
|
+
return {
|
|
8299
|
+
rows: [],
|
|
8300
|
+
rowCount: 0
|
|
8301
|
+
};
|
|
8415
8302
|
}
|
|
8416
|
-
function
|
|
8417
|
-
|
|
8303
|
+
function deriveInsertedIds(options) {
|
|
8304
|
+
const { idColumn, inputRows, returnedRows, rowCount, insertId, lastInsertRowid } = options;
|
|
8305
|
+
if (returnedRows.length > 0) {
|
|
8306
|
+
const idsFromReturn = returnedRows.map((row) => isPlainObject(row) ? row[idColumn] : void 0).filter((id) => typeof id === "number" || typeof id === "string");
|
|
8307
|
+
if (idsFromReturn.length > 0) {
|
|
8308
|
+
return idsFromReturn;
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
8311
|
+
if (inputRows.every((row) => typeof row[idColumn] === "number" || typeof row[idColumn] === "string")) {
|
|
8312
|
+
return inputRows.map((row) => row[idColumn]);
|
|
8313
|
+
}
|
|
8314
|
+
if (insertId !== void 0 && rowCount > 0) {
|
|
8315
|
+
return Array.from({ length: rowCount }, (_, index) => insertId + index);
|
|
8316
|
+
}
|
|
8317
|
+
if (lastInsertRowid !== void 0 && rowCount > 0) {
|
|
8318
|
+
const startId = lastInsertRowid - rowCount + 1;
|
|
8319
|
+
return Array.from({ length: rowCount }, (_, index) => startId + index);
|
|
8320
|
+
}
|
|
8321
|
+
return [];
|
|
8418
8322
|
}
|
|
8419
8323
|
function resolveBatchOptions2(batch) {
|
|
8420
8324
|
if (!batch || batch.enabled === false) {
|
|
@@ -8429,88 +8333,79 @@ function resolveBatchOptions2(batch) {
|
|
|
8429
8333
|
benchmark: batch.benchmark ?? false
|
|
8430
8334
|
};
|
|
8431
8335
|
}
|
|
8432
|
-
function
|
|
8433
|
-
|
|
8434
|
-
throw new Error("UPDATE data is required when operations[] is not provided.");
|
|
8435
|
-
}
|
|
8436
|
-
return {
|
|
8437
|
-
data: input.data,
|
|
8438
|
-
where: input.where,
|
|
8439
|
-
allowFullTableUpdate: input.allowFullTableUpdate,
|
|
8440
|
-
optimisticLock: input.optimisticLock
|
|
8441
|
-
};
|
|
8336
|
+
function toSingleInsertRows(row) {
|
|
8337
|
+
return [row];
|
|
8442
8338
|
}
|
|
8443
8339
|
|
|
8444
|
-
// src/data/relational/tools/relational-
|
|
8445
|
-
async function
|
|
8446
|
-
const built =
|
|
8340
|
+
// src/data/relational/tools/relational-insert/executor-single.ts
|
|
8341
|
+
async function executeInsertOnce(executor, input, context) {
|
|
8342
|
+
const built = buildInsertQuery({
|
|
8447
8343
|
table: input.table,
|
|
8448
|
-
data:
|
|
8449
|
-
|
|
8450
|
-
allowFullTableUpdate: operation.allowFullTableUpdate,
|
|
8451
|
-
optimisticLock: operation.optimisticLock,
|
|
8344
|
+
data: input.data,
|
|
8345
|
+
returning: input.returning,
|
|
8452
8346
|
vendor: input.vendor
|
|
8453
8347
|
});
|
|
8454
|
-
const
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8348
|
+
const session = context?.transaction ?? executor;
|
|
8349
|
+
if (Array.isArray(built.query)) {
|
|
8350
|
+
let totalRowCount = 0;
|
|
8351
|
+
const allRows = [];
|
|
8352
|
+
const allIds = [];
|
|
8353
|
+
for (const singleQuery of built.query) {
|
|
8354
|
+
const rawResult2 = await session.execute(singleQuery);
|
|
8355
|
+
const normalized2 = normalizeExecutionResult(rawResult2);
|
|
8356
|
+
totalRowCount += normalized2.rowCount > 0 ? normalized2.rowCount : 1;
|
|
8357
|
+
if (built.returningMode === "row") {
|
|
8358
|
+
allRows.push(...normalized2.rows);
|
|
8359
|
+
}
|
|
8360
|
+
if (built.returningMode === "id") {
|
|
8361
|
+
const ids = deriveInsertedIds({
|
|
8362
|
+
idColumn: built.idColumn,
|
|
8363
|
+
inputRows: toSingleInsertRows(built.rows[allIds.length]),
|
|
8364
|
+
returnedRows: normalized2.rows,
|
|
8365
|
+
rowCount: 1,
|
|
8366
|
+
insertId: normalized2.insertId,
|
|
8367
|
+
lastInsertRowid: normalized2.lastInsertRowid
|
|
8368
|
+
});
|
|
8369
|
+
allIds.push(...ids);
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
return {
|
|
8373
|
+
rowCount: totalRowCount,
|
|
8374
|
+
rows: allRows,
|
|
8375
|
+
insertedIds: allIds,
|
|
8376
|
+
executionTime: 0
|
|
8377
|
+
};
|
|
8459
8378
|
}
|
|
8379
|
+
const rawResult = await session.execute(built.query);
|
|
8380
|
+
const normalized = normalizeExecutionResult(rawResult);
|
|
8381
|
+
const rowCount = normalized.rowCount > 0 ? normalized.rowCount : built.rows.length;
|
|
8382
|
+
const rows = built.returningMode === "row" ? normalized.rows : [];
|
|
8383
|
+
const insertedIds = built.returningMode === "id" ? deriveInsertedIds({
|
|
8384
|
+
idColumn: built.idColumn,
|
|
8385
|
+
inputRows: built.rows,
|
|
8386
|
+
returnedRows: normalized.rows,
|
|
8387
|
+
rowCount,
|
|
8388
|
+
insertId: normalized.insertId,
|
|
8389
|
+
lastInsertRowid: normalized.lastInsertRowid
|
|
8390
|
+
}) : [];
|
|
8460
8391
|
return {
|
|
8461
8392
|
rowCount,
|
|
8393
|
+
rows,
|
|
8394
|
+
insertedIds,
|
|
8462
8395
|
executionTime: 0
|
|
8463
8396
|
};
|
|
8464
8397
|
}
|
|
8465
8398
|
|
|
8466
|
-
// src/data/relational/tools/relational-
|
|
8467
|
-
async function
|
|
8399
|
+
// src/data/relational/tools/relational-insert/executor-batch.ts
|
|
8400
|
+
async function executeInsertInBatchMode(executor, input, context, options) {
|
|
8468
8401
|
const batchResult = await executeBatchedTask(
|
|
8469
8402
|
{
|
|
8470
|
-
operation: "
|
|
8471
|
-
items: input.
|
|
8472
|
-
executeBatch: async (
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
const failures = [];
|
|
8477
|
-
for (const operation of operations) {
|
|
8478
|
-
try {
|
|
8479
|
-
const result = await executeSingleUpdate(
|
|
8480
|
-
manager,
|
|
8481
|
-
input,
|
|
8482
|
-
{
|
|
8483
|
-
data: operation.data,
|
|
8484
|
-
where: operation.where,
|
|
8485
|
-
allowFullTableUpdate: operation.allowFullTableUpdate,
|
|
8486
|
-
optimisticLock: operation.optimisticLock
|
|
8487
|
-
},
|
|
8488
|
-
context
|
|
8489
|
-
);
|
|
8490
|
-
rowCount2 += result.rowCount;
|
|
8491
|
-
successfulItems += 1;
|
|
8492
|
-
} catch (error) {
|
|
8493
|
-
if (!options.continueOnError) {
|
|
8494
|
-
throw error;
|
|
8495
|
-
}
|
|
8496
|
-
failedItems += 1;
|
|
8497
|
-
failures.push({
|
|
8498
|
-
operation: "update",
|
|
8499
|
-
batchIndex,
|
|
8500
|
-
batchSize: operations.length,
|
|
8501
|
-
attempts: 1,
|
|
8502
|
-
error: toErrorMessage(error)
|
|
8503
|
-
});
|
|
8504
|
-
}
|
|
8505
|
-
}
|
|
8506
|
-
return {
|
|
8507
|
-
rowCount: rowCount2,
|
|
8508
|
-
successfulItems,
|
|
8509
|
-
failedItems,
|
|
8510
|
-
failures
|
|
8511
|
-
};
|
|
8512
|
-
},
|
|
8513
|
-
getBatchSuccessCount: (result) => result.successfulItems
|
|
8403
|
+
operation: "insert",
|
|
8404
|
+
items: input.data,
|
|
8405
|
+
executeBatch: async (rows2) => executeInsertOnce(executor, {
|
|
8406
|
+
...input,
|
|
8407
|
+
data: rows2}, context),
|
|
8408
|
+
getBatchSuccessCount: (result, rows2) => Math.min(result.rowCount, rows2.length)
|
|
8514
8409
|
},
|
|
8515
8410
|
{
|
|
8516
8411
|
batchSize: options.batchSize,
|
|
@@ -8518,7 +8413,7 @@ async function executeUpdateInBatchMode(manager, input, context, options) {
|
|
|
8518
8413
|
maxRetries: options.maxRetries,
|
|
8519
8414
|
retryDelayMs: options.retryDelayMs,
|
|
8520
8415
|
onProgress: (progress) => {
|
|
8521
|
-
|
|
8416
|
+
insertExecutorLogger.debug("INSERT batch progress", {
|
|
8522
8417
|
vendor: input.vendor,
|
|
8523
8418
|
table: input.table,
|
|
8524
8419
|
...progress
|
|
@@ -8527,165 +8422,601 @@ async function executeUpdateInBatchMode(manager, input, context, options) {
|
|
|
8527
8422
|
}
|
|
8528
8423
|
);
|
|
8529
8424
|
const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
|
|
8530
|
-
const
|
|
8425
|
+
const rows = batchResult.results.flatMap((result) => result.rows);
|
|
8426
|
+
const insertedIds = batchResult.results.flatMap((result) => result.insertedIds);
|
|
8531
8427
|
let benchmark;
|
|
8532
8428
|
if (options.benchmark) {
|
|
8533
|
-
|
|
8429
|
+
insertExecutorLogger.warn("INSERT batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
|
|
8534
8430
|
vendor: input.vendor,
|
|
8535
8431
|
table: input.table,
|
|
8536
|
-
|
|
8432
|
+
totalRows: input.data.length,
|
|
8537
8433
|
batchSize: options.batchSize
|
|
8538
8434
|
});
|
|
8539
|
-
benchmark = await benchmarkBatchExecution({
|
|
8540
|
-
items: input.
|
|
8541
|
-
batchSize: options.batchSize,
|
|
8542
|
-
runIndividual: async () => {
|
|
8543
|
-
},
|
|
8544
|
-
runBatch: async () => {
|
|
8545
|
-
}
|
|
8435
|
+
benchmark = await benchmarkBatchExecution({
|
|
8436
|
+
items: input.data,
|
|
8437
|
+
batchSize: options.batchSize,
|
|
8438
|
+
runIndividual: async () => {
|
|
8439
|
+
},
|
|
8440
|
+
runBatch: async () => {
|
|
8441
|
+
}
|
|
8442
|
+
});
|
|
8443
|
+
}
|
|
8444
|
+
return {
|
|
8445
|
+
rowCount,
|
|
8446
|
+
rows,
|
|
8447
|
+
insertedIds,
|
|
8448
|
+
executionTime: batchResult.executionTime,
|
|
8449
|
+
batch: {
|
|
8450
|
+
enabled: true,
|
|
8451
|
+
batchSize: options.batchSize,
|
|
8452
|
+
totalItems: batchResult.totalItems,
|
|
8453
|
+
processedItems: batchResult.processedItems,
|
|
8454
|
+
successfulItems: batchResult.successfulItems,
|
|
8455
|
+
failedItems: batchResult.failedItems,
|
|
8456
|
+
totalBatches: batchResult.totalBatches,
|
|
8457
|
+
retries: batchResult.retries,
|
|
8458
|
+
partialSuccess: batchResult.partialSuccess,
|
|
8459
|
+
failures: batchResult.failures,
|
|
8460
|
+
benchmark
|
|
8461
|
+
}
|
|
8462
|
+
};
|
|
8463
|
+
}
|
|
8464
|
+
|
|
8465
|
+
// src/data/relational/tools/relational-insert/executor.ts
|
|
8466
|
+
function getCauseMetadata(error) {
|
|
8467
|
+
if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") {
|
|
8468
|
+
return {};
|
|
8469
|
+
}
|
|
8470
|
+
const cause = error.cause;
|
|
8471
|
+
return {
|
|
8472
|
+
causeType: error.cause instanceof Error ? error.cause.name : "object",
|
|
8473
|
+
...typeof cause.code === "string" ? { causeCode: cause.code } : {},
|
|
8474
|
+
...typeof cause.constraint === "string" ? { causeConstraint: cause.constraint } : {}
|
|
8475
|
+
};
|
|
8476
|
+
}
|
|
8477
|
+
async function executeInsert(executor, input, context) {
|
|
8478
|
+
const startTime = Date.now();
|
|
8479
|
+
insertExecutorLogger.debug("Building INSERT query", {
|
|
8480
|
+
vendor: input.vendor,
|
|
8481
|
+
table: input.table,
|
|
8482
|
+
isBatch: Array.isArray(input.data),
|
|
8483
|
+
batchModeEnabled: !!input.batch?.enabled,
|
|
8484
|
+
returningMode: input.returning?.mode ?? "none"
|
|
8485
|
+
});
|
|
8486
|
+
try {
|
|
8487
|
+
const batchOptions = resolveBatchOptions2(input.batch);
|
|
8488
|
+
const result = Array.isArray(input.data) && batchOptions ? await executeInsertInBatchMode(
|
|
8489
|
+
executor,
|
|
8490
|
+
input,
|
|
8491
|
+
context,
|
|
8492
|
+
batchOptions
|
|
8493
|
+
) : await executeInsertOnce(executor, input, context);
|
|
8494
|
+
const executionTime = Date.now() - startTime;
|
|
8495
|
+
insertExecutorLogger.debug("INSERT query executed successfully", {
|
|
8496
|
+
vendor: input.vendor,
|
|
8497
|
+
table: input.table,
|
|
8498
|
+
rowCount: result.rowCount,
|
|
8499
|
+
returnedRows: result.rows.length,
|
|
8500
|
+
returnedIds: result.insertedIds.length,
|
|
8501
|
+
executionTime,
|
|
8502
|
+
batchMode: !!result.batch,
|
|
8503
|
+
partialSuccess: result.batch?.partialSuccess ?? false
|
|
8504
|
+
});
|
|
8505
|
+
return {
|
|
8506
|
+
...result,
|
|
8507
|
+
executionTime
|
|
8508
|
+
};
|
|
8509
|
+
} catch (error) {
|
|
8510
|
+
const executionTime = Date.now() - startTime;
|
|
8511
|
+
insertExecutorLogger.error("INSERT query execution failed", {
|
|
8512
|
+
vendor: input.vendor,
|
|
8513
|
+
table: input.table,
|
|
8514
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8515
|
+
...getCauseMetadata(error),
|
|
8516
|
+
executionTime
|
|
8517
|
+
});
|
|
8518
|
+
const constraintMessage = getConstraintViolationMessage(error);
|
|
8519
|
+
if (constraintMessage) {
|
|
8520
|
+
throw new Error(constraintMessage, { cause: error });
|
|
8521
|
+
}
|
|
8522
|
+
if (isSafeInsertValidationError(error)) {
|
|
8523
|
+
throw error;
|
|
8524
|
+
}
|
|
8525
|
+
throw new Error("INSERT query failed. See logs for details.", { cause: error });
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
|
|
8529
|
+
// src/data/relational/tools/relational-insert/index.ts
|
|
8530
|
+
function toInsertErrorResponse(error) {
|
|
8531
|
+
const errorMessage = isSafeInsertError(error) ? error.message : "Failed to execute INSERT query. Please verify your input and database connection.";
|
|
8532
|
+
return {
|
|
8533
|
+
success: false,
|
|
8534
|
+
error: errorMessage,
|
|
8535
|
+
rowCount: 0,
|
|
8536
|
+
insertedIds: [],
|
|
8537
|
+
rows: []
|
|
8538
|
+
};
|
|
8539
|
+
}
|
|
8540
|
+
async function invokeRelationalInsert(execution, input) {
|
|
8541
|
+
try {
|
|
8542
|
+
const result = await executeInsert(
|
|
8543
|
+
execution.executor,
|
|
8544
|
+
{ ...input, vendor: execution.vendor },
|
|
8545
|
+
execution.transaction ? { transaction: execution.transaction } : void 0
|
|
8546
|
+
);
|
|
8547
|
+
return {
|
|
8548
|
+
success: true,
|
|
8549
|
+
rowCount: result.rowCount,
|
|
8550
|
+
insertedIds: result.insertedIds,
|
|
8551
|
+
rows: result.rows,
|
|
8552
|
+
executionTime: result.executionTime,
|
|
8553
|
+
batch: result.batch
|
|
8554
|
+
};
|
|
8555
|
+
} catch (error) {
|
|
8556
|
+
return toInsertErrorResponse(error);
|
|
8557
|
+
}
|
|
8558
|
+
}
|
|
8559
|
+
var relationalInsert = toolBuilder().name("relational-insert").displayName("Relational INSERT").description("Execute type-safe INSERT queries with single-row and batch insert support").category(ToolCategory.DATABASE).tags(["database", "sql", "insert", "postgresql", "mysql", "sqlite"]).schema(relationalInsertSchema).example({
|
|
8560
|
+
description: "Insert a single row and return generated ID",
|
|
8561
|
+
input: {
|
|
8562
|
+
table: "users",
|
|
8563
|
+
data: { name: "Alice", email: "alice@example.com" },
|
|
8564
|
+
returning: { mode: "id", idColumn: "id" },
|
|
8565
|
+
vendor: "postgresql",
|
|
8566
|
+
connectionString: "postgresql://localhost/mydb"
|
|
8567
|
+
}
|
|
8568
|
+
}).example({
|
|
8569
|
+
description: "Batch insert rows",
|
|
8570
|
+
input: {
|
|
8571
|
+
table: "users",
|
|
8572
|
+
data: [
|
|
8573
|
+
{ name: "Bob", email: "bob@example.com" },
|
|
8574
|
+
{ name: "Carol", email: "carol@example.com" }
|
|
8575
|
+
],
|
|
8576
|
+
batch: {
|
|
8577
|
+
batchSize: 250,
|
|
8578
|
+
continueOnError: true,
|
|
8579
|
+
maxRetries: 1
|
|
8580
|
+
},
|
|
8581
|
+
vendor: "sqlite",
|
|
8582
|
+
connectionString: "data.db"
|
|
8583
|
+
}
|
|
8584
|
+
}).implement(async (input) => {
|
|
8585
|
+
const { connectionString, vendor, ...operation } = input;
|
|
8586
|
+
return withEphemeralRelationalMutationToolSet(
|
|
8587
|
+
{ vendor, connectionString },
|
|
8588
|
+
async (toolSet) => replaceMutationConnectionFailure(
|
|
8589
|
+
await toolSet.insert.invoke(operation),
|
|
8590
|
+
INSERT_CONNECTION_FAILURE,
|
|
8591
|
+
"Failed to execute INSERT query. Please verify your input and database connection."
|
|
8592
|
+
),
|
|
8593
|
+
toInsertErrorResponse
|
|
8594
|
+
);
|
|
8595
|
+
}).build();
|
|
8596
|
+
var relationalQuerySchema = z.object({
|
|
8597
|
+
sql: z.string().describe("SQL query string to execute"),
|
|
8598
|
+
params: z.union([
|
|
8599
|
+
z.array(z.unknown()).describe("Positional parameters (e.g., [value1, value2])"),
|
|
8600
|
+
z.record(z.string(), z.unknown()).describe('Named parameters (e.g., { name: "John", age: 30 })')
|
|
8601
|
+
]).optional().describe("Query parameters for parameter binding (prevents SQL injection)"),
|
|
8602
|
+
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
8603
|
+
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string used to create a new connection")
|
|
8604
|
+
});
|
|
8605
|
+
function toQueryErrorResponse(error) {
|
|
8606
|
+
return {
|
|
8607
|
+
success: false,
|
|
8608
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
8609
|
+
rows: [],
|
|
8610
|
+
rowCount: 0
|
|
8611
|
+
};
|
|
8612
|
+
}
|
|
8613
|
+
async function invokeRelationalQuery(execution, input) {
|
|
8614
|
+
try {
|
|
8615
|
+
const queryInput = {
|
|
8616
|
+
sql: input.sql,
|
|
8617
|
+
params: input.params,
|
|
8618
|
+
vendor: execution.vendor
|
|
8619
|
+
};
|
|
8620
|
+
const result = execution.transaction ? await executeQuery2(execution.executor, queryInput, { transaction: execution.transaction }) : await executeQuery2(execution.executor, queryInput);
|
|
8621
|
+
return {
|
|
8622
|
+
success: true,
|
|
8623
|
+
rows: result.rows,
|
|
8624
|
+
rowCount: result.rowCount,
|
|
8625
|
+
executionTime: result.executionTime
|
|
8626
|
+
};
|
|
8627
|
+
} catch (error) {
|
|
8628
|
+
return toQueryErrorResponse(error);
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
var relationalQuery = 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(ToolCategory.DATABASE).tags(["database", "sql", "query", "postgresql", "mysql", "sqlite"]).schema(relationalQuerySchema).example({
|
|
8632
|
+
description: "SELECT query with positional parameters",
|
|
8633
|
+
input: {
|
|
8634
|
+
sql: "SELECT * FROM users WHERE id = $1",
|
|
8635
|
+
params: [42],
|
|
8636
|
+
vendor: "postgresql",
|
|
8637
|
+
connectionString: "postgresql://user:pass@localhost:5432/mydb"
|
|
8638
|
+
}
|
|
8639
|
+
}).example({
|
|
8640
|
+
description: "INSERT query with named parameters",
|
|
8641
|
+
input: {
|
|
8642
|
+
sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
|
|
8643
|
+
params: { name: "John Doe", email: "john@example.com" },
|
|
8644
|
+
vendor: "postgresql",
|
|
8645
|
+
connectionString: "postgresql://user:pass@localhost:5432/mydb"
|
|
8646
|
+
}
|
|
8647
|
+
}).example({
|
|
8648
|
+
description: "UPDATE query with positional parameters",
|
|
8649
|
+
input: {
|
|
8650
|
+
sql: "UPDATE users SET status = ? WHERE id = ?",
|
|
8651
|
+
params: ["active", 42],
|
|
8652
|
+
vendor: "mysql",
|
|
8653
|
+
connectionString: "mysql://user:pass@localhost:3306/mydb"
|
|
8654
|
+
}
|
|
8655
|
+
}).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) => {
|
|
8656
|
+
const { connectionString, vendor, ...operation } = input;
|
|
8657
|
+
return withEphemeralRelationalToolSet(
|
|
8658
|
+
{ vendor, connectionString },
|
|
8659
|
+
async (toolSet) => replaceConnectionFailureMessage(
|
|
8660
|
+
await toolSet.query.invoke(operation),
|
|
8661
|
+
QUERY_CONNECTION_FAILURE,
|
|
8662
|
+
`Failed to initialize ${vendor} connection`
|
|
8663
|
+
)
|
|
8664
|
+
);
|
|
8665
|
+
}).build();
|
|
8666
|
+
var whereOperatorSchema = z.enum([
|
|
8667
|
+
"eq",
|
|
8668
|
+
// equals
|
|
8669
|
+
"ne",
|
|
8670
|
+
// not equals
|
|
8671
|
+
"gt",
|
|
8672
|
+
// greater than
|
|
8673
|
+
"lt",
|
|
8674
|
+
// less than
|
|
8675
|
+
"gte",
|
|
8676
|
+
// greater than or equal
|
|
8677
|
+
"lte",
|
|
8678
|
+
// less than or equal
|
|
8679
|
+
"like",
|
|
8680
|
+
// LIKE pattern matching
|
|
8681
|
+
"in",
|
|
8682
|
+
// IN array
|
|
8683
|
+
"notIn",
|
|
8684
|
+
// NOT IN array
|
|
8685
|
+
"isNull",
|
|
8686
|
+
// IS NULL
|
|
8687
|
+
"isNotNull"
|
|
8688
|
+
// IS NOT NULL
|
|
8689
|
+
]);
|
|
8690
|
+
var whereConditionSchema = z.object({
|
|
8691
|
+
column: z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
|
|
8692
|
+
operator: whereOperatorSchema.describe("Comparison operator"),
|
|
8693
|
+
value: z.union([
|
|
8694
|
+
z.string(),
|
|
8695
|
+
z.number(),
|
|
8696
|
+
z.boolean(),
|
|
8697
|
+
z.array(z.union([z.string(), z.number()])),
|
|
8698
|
+
z.null()
|
|
8699
|
+
]).optional().describe("Value to compare against (not required for isNull/isNotNull)")
|
|
8700
|
+
}).superRefine((val, ctx) => {
|
|
8701
|
+
const op = val.operator;
|
|
8702
|
+
if (op === "isNull" || op === "isNotNull") {
|
|
8703
|
+
if (val.value !== void 0) {
|
|
8704
|
+
ctx.addIssue({
|
|
8705
|
+
code: z.ZodIssueCode.custom,
|
|
8706
|
+
path: ["value"],
|
|
8707
|
+
message: "value must not be provided when using isNull or isNotNull operator"
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
8710
|
+
return;
|
|
8711
|
+
}
|
|
8712
|
+
if (val.value === void 0) {
|
|
8713
|
+
ctx.addIssue({
|
|
8714
|
+
code: z.ZodIssueCode.custom,
|
|
8715
|
+
path: ["value"],
|
|
8716
|
+
message: "value is required for this operator"
|
|
8717
|
+
});
|
|
8718
|
+
return;
|
|
8719
|
+
}
|
|
8720
|
+
if (op === "in" || op === "notIn") {
|
|
8721
|
+
if (!Array.isArray(val.value)) {
|
|
8722
|
+
ctx.addIssue({
|
|
8723
|
+
code: z.ZodIssueCode.custom,
|
|
8724
|
+
path: ["value"],
|
|
8725
|
+
message: `${op === "in" ? "IN" : "NOT IN"} operator requires an array value`
|
|
8726
|
+
});
|
|
8727
|
+
} else if (val.value.length === 0) {
|
|
8728
|
+
ctx.addIssue({
|
|
8729
|
+
code: z.ZodIssueCode.custom,
|
|
8730
|
+
path: ["value"],
|
|
8731
|
+
message: `${op === "in" ? "IN" : "NOT IN"} operator requires a non-empty array value`
|
|
8732
|
+
});
|
|
8733
|
+
}
|
|
8734
|
+
return;
|
|
8735
|
+
}
|
|
8736
|
+
if (val.value === null) {
|
|
8737
|
+
ctx.addIssue({
|
|
8738
|
+
code: z.ZodIssueCode.custom,
|
|
8739
|
+
path: ["value"],
|
|
8740
|
+
message: "null is only allowed with isNull/isNotNull operators"
|
|
8741
|
+
});
|
|
8742
|
+
return;
|
|
8743
|
+
}
|
|
8744
|
+
if (op === "like" && typeof val.value !== "string") {
|
|
8745
|
+
ctx.addIssue({
|
|
8746
|
+
code: z.ZodIssueCode.custom,
|
|
8747
|
+
path: ["value"],
|
|
8748
|
+
message: "LIKE operator requires a string value"
|
|
8749
|
+
});
|
|
8750
|
+
return;
|
|
8751
|
+
}
|
|
8752
|
+
if ((op === "gt" || op === "lt" || op === "gte" || op === "lte") && typeof val.value !== "string" && typeof val.value !== "number") {
|
|
8753
|
+
ctx.addIssue({
|
|
8754
|
+
code: z.ZodIssueCode.custom,
|
|
8755
|
+
path: ["value"],
|
|
8756
|
+
message: `${op.toUpperCase()} operator requires a string or number value`
|
|
8546
8757
|
});
|
|
8758
|
+
return;
|
|
8547
8759
|
}
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8760
|
+
if ((op === "eq" || op === "ne") && Array.isArray(val.value)) {
|
|
8761
|
+
ctx.addIssue({
|
|
8762
|
+
code: z.ZodIssueCode.custom,
|
|
8763
|
+
path: ["value"],
|
|
8764
|
+
message: `${op.toUpperCase()} operator requires a scalar value`
|
|
8765
|
+
});
|
|
8766
|
+
}
|
|
8767
|
+
});
|
|
8768
|
+
var orderDirectionSchema = z.enum(["asc", "desc"]);
|
|
8769
|
+
var orderBySchema = z.object({
|
|
8770
|
+
column: z.string().min(1, "Column name must not be empty").describe("Column name to order by"),
|
|
8771
|
+
direction: orderDirectionSchema.describe("Sort direction (ascending or descending)")
|
|
8772
|
+
});
|
|
8773
|
+
var streamingOptionsSchema = z.object({
|
|
8774
|
+
enabled: z.boolean().default(true).describe("Enable chunked streaming mode for large result sets"),
|
|
8775
|
+
chunkSize: z.number().int().min(1).max(5e3).optional().describe("Rows fetched per chunk (default: 100)"),
|
|
8776
|
+
maxRows: z.number().int().positive().optional().describe("Optional cap on streamed rows"),
|
|
8777
|
+
sampleSize: z.number().int().min(0).max(5e3).optional().describe("Number of rows to include in the response payload"),
|
|
8778
|
+
benchmark: 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)")
|
|
8779
|
+
});
|
|
8780
|
+
var relationalSelectSchema = z.object({
|
|
8781
|
+
table: z.string().min(1, "Table name is required").regex(
|
|
8782
|
+
VALID_QUALIFIED_IDENTIFIER_PATTERN,
|
|
8783
|
+
"Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
|
|
8784
|
+
).describe("Table name to select from (schema-qualified names supported, e.g. public.users)"),
|
|
8785
|
+
columns: z.array(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 *)"),
|
|
8786
|
+
where: z.array(whereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
8787
|
+
orderBy: z.array(orderBySchema).optional().describe("ORDER BY clauses"),
|
|
8788
|
+
limit: z.number().int().positive().optional().describe("Maximum number of rows to return"),
|
|
8789
|
+
offset: z.number().int().nonnegative().optional().describe("Number of rows to skip"),
|
|
8790
|
+
streaming: streamingOptionsSchema.optional().describe("Optional streaming mode configuration"),
|
|
8791
|
+
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
8792
|
+
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string")
|
|
8793
|
+
});
|
|
8794
|
+
|
|
8795
|
+
// src/data/relational/tools/relational-select/query-builder.ts
|
|
8796
|
+
function buildSelectQuery2(input) {
|
|
8797
|
+
const queryInput = {
|
|
8798
|
+
table: input.table,
|
|
8799
|
+
columns: input.columns,
|
|
8800
|
+
where: input.where,
|
|
8801
|
+
orderBy: input.orderBy,
|
|
8802
|
+
limit: input.limit,
|
|
8803
|
+
offset: input.offset,
|
|
8804
|
+
vendor: input.vendor
|
|
8564
8805
|
};
|
|
8806
|
+
return buildSelectQuery(queryInput);
|
|
8565
8807
|
}
|
|
8566
8808
|
|
|
8567
|
-
// src/data/relational/tools/relational-
|
|
8568
|
-
|
|
8809
|
+
// src/data/relational/tools/relational-select/error-utils.ts
|
|
8810
|
+
var SAFE_VALIDATION_ERROR_PATTERNS2 = [
|
|
8811
|
+
"must not be empty",
|
|
8812
|
+
"contains invalid characters",
|
|
8813
|
+
"requires an array value",
|
|
8814
|
+
"requires a non-empty array value",
|
|
8815
|
+
"must be an integer between",
|
|
8816
|
+
"must be a positive integer",
|
|
8817
|
+
"null is only allowed with isNull/isNotNull operators",
|
|
8818
|
+
"LIKE operator requires a string value",
|
|
8819
|
+
"operator requires a string or number value",
|
|
8820
|
+
"operator requires a scalar value",
|
|
8821
|
+
"Stream cancelled by caller"
|
|
8822
|
+
];
|
|
8823
|
+
function isSafeValidationError(error) {
|
|
8824
|
+
if (!(error instanceof Error)) {
|
|
8825
|
+
return false;
|
|
8826
|
+
}
|
|
8827
|
+
return SAFE_VALIDATION_ERROR_PATTERNS2.some(
|
|
8828
|
+
(pattern) => error.message.includes(pattern)
|
|
8829
|
+
);
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
// src/data/relational/tools/relational-select/executor.ts
|
|
8833
|
+
var logger17 = createLogger("agentforge:tools:data:relational:select");
|
|
8834
|
+
function toSelectQueryInput(input) {
|
|
8835
|
+
return {
|
|
8836
|
+
table: input.table,
|
|
8837
|
+
columns: input.columns,
|
|
8838
|
+
where: input.where,
|
|
8839
|
+
orderBy: input.orderBy,
|
|
8840
|
+
limit: input.limit,
|
|
8841
|
+
offset: input.offset,
|
|
8842
|
+
vendor: input.vendor
|
|
8843
|
+
};
|
|
8844
|
+
}
|
|
8845
|
+
async function executeSelect(executor, input, context) {
|
|
8569
8846
|
const startTime = Date.now();
|
|
8570
|
-
|
|
8847
|
+
logger17.debug("Building SELECT query", {
|
|
8571
8848
|
vendor: input.vendor,
|
|
8572
8849
|
table: input.table,
|
|
8573
|
-
hasWhere: !!input.where
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
batchModeEnabled: !!input.batch?.enabled
|
|
8850
|
+
hasWhere: !!input.where,
|
|
8851
|
+
hasOrderBy: !!input.orderBy,
|
|
8852
|
+
hasLimit: !!input.limit,
|
|
8853
|
+
streamingEnabled: !!input.streaming?.enabled
|
|
8578
8854
|
});
|
|
8579
8855
|
try {
|
|
8580
|
-
const
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8856
|
+
const session = context?.transaction ?? executor;
|
|
8857
|
+
if (input.streaming?.enabled) {
|
|
8858
|
+
const streamOptions = {
|
|
8859
|
+
chunkSize: input.streaming.chunkSize,
|
|
8860
|
+
maxRows: input.streaming.maxRows,
|
|
8861
|
+
sampleSize: input.streaming.sampleSize
|
|
8862
|
+
};
|
|
8863
|
+
const streamInput = toSelectQueryInput(input);
|
|
8864
|
+
if (input.streaming.benchmark) {
|
|
8865
|
+
logger17.warn("Streaming benchmark enabled; SELECT will execute up to three times (result + benchmark regular + benchmark streaming).", {
|
|
8866
|
+
vendor: input.vendor,
|
|
8867
|
+
table: input.table,
|
|
8868
|
+
chunkSize: streamOptions.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
8869
|
+
...streamOptions.maxRows !== void 0 ? { maxRows: streamOptions.maxRows } : {},
|
|
8870
|
+
...streamOptions.sampleSize !== void 0 ? { sampleSize: streamOptions.sampleSize } : {}
|
|
8871
|
+
});
|
|
8872
|
+
}
|
|
8873
|
+
const streamResult = await executeStreamingSelect(session, streamInput, streamOptions);
|
|
8874
|
+
const benchmark = input.streaming.benchmark ? await benchmarkStreamingSelectMemory(session, streamInput, streamOptions) : void 0;
|
|
8875
|
+
const executionTime2 = Date.now() - startTime;
|
|
8876
|
+
logger17.debug("Streaming SELECT query executed successfully", {
|
|
8877
|
+
vendor: input.vendor,
|
|
8878
|
+
table: input.table,
|
|
8879
|
+
rowCount: streamResult.rowCount,
|
|
8880
|
+
chunkCount: streamResult.chunkCount,
|
|
8881
|
+
executionTime: executionTime2,
|
|
8882
|
+
cancelled: streamResult.cancelled
|
|
8883
|
+
});
|
|
8884
|
+
return {
|
|
8885
|
+
rows: streamResult.rows,
|
|
8886
|
+
rowCount: streamResult.rowCount,
|
|
8887
|
+
executionTime: executionTime2,
|
|
8888
|
+
streaming: {
|
|
8889
|
+
enabled: true,
|
|
8890
|
+
chunkSize: input.streaming.chunkSize ?? DEFAULT_CHUNK_SIZE,
|
|
8891
|
+
chunkCount: streamResult.chunkCount,
|
|
8892
|
+
sampledRowCount: streamResult.rows.length,
|
|
8893
|
+
streamedRowCount: streamResult.rowCount,
|
|
8894
|
+
cancelled: streamResult.cancelled,
|
|
8895
|
+
memoryUsage: streamResult.memoryUsage,
|
|
8896
|
+
benchmark
|
|
8897
|
+
}
|
|
8898
|
+
};
|
|
8899
|
+
}
|
|
8900
|
+
const query = buildSelectQuery2(input);
|
|
8901
|
+
const result = await session.execute(query);
|
|
8587
8902
|
const executionTime = Date.now() - startTime;
|
|
8588
|
-
|
|
8903
|
+
const rows = Array.isArray(result) ? result : result.rows || [];
|
|
8904
|
+
const rowCount = rows.length;
|
|
8905
|
+
logger17.debug("SELECT query executed successfully", {
|
|
8589
8906
|
vendor: input.vendor,
|
|
8590
8907
|
table: input.table,
|
|
8591
|
-
rowCount
|
|
8592
|
-
executionTime
|
|
8593
|
-
batchMode: !!result.batch,
|
|
8594
|
-
partialSuccess: result.batch?.partialSuccess ?? false
|
|
8908
|
+
rowCount,
|
|
8909
|
+
executionTime
|
|
8595
8910
|
});
|
|
8596
8911
|
return {
|
|
8597
|
-
|
|
8912
|
+
rows,
|
|
8913
|
+
rowCount,
|
|
8598
8914
|
executionTime
|
|
8599
8915
|
};
|
|
8600
8916
|
} catch (error) {
|
|
8601
8917
|
const executionTime = Date.now() - startTime;
|
|
8602
|
-
|
|
8918
|
+
logger17.error("SELECT query execution failed", {
|
|
8603
8919
|
vendor: input.vendor,
|
|
8604
8920
|
table: input.table,
|
|
8605
8921
|
error: error instanceof Error ? error.message : String(error),
|
|
8606
8922
|
executionTime
|
|
8607
8923
|
});
|
|
8608
|
-
|
|
8609
|
-
if (constraintMessage) {
|
|
8610
|
-
throw new Error(constraintMessage, { cause: error });
|
|
8611
|
-
}
|
|
8612
|
-
if (isSafeUpdateValidationError(error)) {
|
|
8924
|
+
if (isSafeValidationError(error)) {
|
|
8613
8925
|
throw error;
|
|
8614
8926
|
}
|
|
8615
|
-
throw new Error("
|
|
8927
|
+
throw new Error("SELECT query failed. See logs for details.", { cause: error });
|
|
8616
8928
|
}
|
|
8617
8929
|
}
|
|
8618
8930
|
|
|
8619
|
-
// src/data/relational/tools/relational-
|
|
8620
|
-
|
|
8621
|
-
|
|
8931
|
+
// src/data/relational/tools/relational-select/index.ts
|
|
8932
|
+
function toSelectErrorResponse(error) {
|
|
8933
|
+
const errorMessage = isSafeValidationError(error) ? error.message : "Failed to execute SELECT query. Please verify your input and database connection.";
|
|
8934
|
+
return {
|
|
8935
|
+
success: false,
|
|
8936
|
+
error: errorMessage,
|
|
8937
|
+
rows: [],
|
|
8938
|
+
rowCount: 0
|
|
8939
|
+
};
|
|
8940
|
+
}
|
|
8941
|
+
async function invokeRelationalSelect(execution, input) {
|
|
8942
|
+
try {
|
|
8943
|
+
const result = await executeSelect(
|
|
8944
|
+
execution.executor,
|
|
8945
|
+
{ ...input, vendor: execution.vendor },
|
|
8946
|
+
execution.transaction ? { transaction: execution.transaction } : void 0
|
|
8947
|
+
);
|
|
8948
|
+
return {
|
|
8949
|
+
success: true,
|
|
8950
|
+
rows: result.rows,
|
|
8951
|
+
rowCount: result.rowCount,
|
|
8952
|
+
executionTime: result.executionTime,
|
|
8953
|
+
streaming: result.streaming
|
|
8954
|
+
};
|
|
8955
|
+
} catch (error) {
|
|
8956
|
+
return toSelectErrorResponse(error);
|
|
8957
|
+
}
|
|
8958
|
+
}
|
|
8959
|
+
var relationalSelect = 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(ToolCategory.DATABASE).tags(["database", "sql", "select", "query", "postgresql", "mysql", "sqlite"]).schema(relationalSelectSchema).example({
|
|
8960
|
+
description: "SELECT with WHERE condition",
|
|
8622
8961
|
input: {
|
|
8623
8962
|
table: "users",
|
|
8624
|
-
|
|
8625
|
-
where: [{ column: "
|
|
8963
|
+
columns: ["id", "name", "email"],
|
|
8964
|
+
where: [{ column: "status", operator: "eq", value: "active" }],
|
|
8626
8965
|
vendor: "postgresql",
|
|
8627
8966
|
connectionString: "postgresql://localhost/mydb"
|
|
8628
8967
|
}
|
|
8629
8968
|
}).example({
|
|
8630
|
-
description: "
|
|
8969
|
+
description: "SELECT with pagination",
|
|
8631
8970
|
input: {
|
|
8632
|
-
table: "
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
vendor: "
|
|
8637
|
-
connectionString: "
|
|
8971
|
+
table: "products",
|
|
8972
|
+
orderBy: [{ column: "created_at", direction: "desc" }],
|
|
8973
|
+
limit: 20,
|
|
8974
|
+
offset: 0,
|
|
8975
|
+
vendor: "mysql",
|
|
8976
|
+
connectionString: "mysql://localhost/shop"
|
|
8638
8977
|
}
|
|
8639
8978
|
}).example({
|
|
8640
|
-
description: "
|
|
8979
|
+
description: "SELECT with streaming mode",
|
|
8641
8980
|
input: {
|
|
8642
|
-
table: "
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
{
|
|
8649
|
-
data: { status: "inactive" },
|
|
8650
|
-
where: [{ column: "id", operator: "eq", value: 2 }]
|
|
8651
|
-
}
|
|
8652
|
-
],
|
|
8653
|
-
batch: {
|
|
8654
|
-
batchSize: 100,
|
|
8655
|
-
continueOnError: true
|
|
8981
|
+
table: "events",
|
|
8982
|
+
orderBy: [{ column: "id", direction: "asc" }],
|
|
8983
|
+
streaming: {
|
|
8984
|
+
enabled: true,
|
|
8985
|
+
chunkSize: 250,
|
|
8986
|
+
sampleSize: 25
|
|
8656
8987
|
},
|
|
8657
8988
|
vendor: "postgresql",
|
|
8658
|
-
connectionString: "postgresql://localhost/
|
|
8989
|
+
connectionString: "postgresql://localhost/analytics"
|
|
8659
8990
|
}
|
|
8660
8991
|
}).implement(async (input) => {
|
|
8661
|
-
const
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
rowCount: result.rowCount,
|
|
8671
|
-
executionTime: result.executionTime,
|
|
8672
|
-
batch: result.batch
|
|
8673
|
-
};
|
|
8674
|
-
} catch (error) {
|
|
8675
|
-
let errorMessage = "Failed to execute UPDATE query. Please verify your input and database connection.";
|
|
8676
|
-
if (isSafeUpdateError(error)) {
|
|
8677
|
-
errorMessage = error.message;
|
|
8678
|
-
}
|
|
8679
|
-
return {
|
|
8680
|
-
success: false,
|
|
8681
|
-
error: errorMessage,
|
|
8682
|
-
rowCount: 0
|
|
8683
|
-
};
|
|
8684
|
-
} finally {
|
|
8685
|
-
await manager.disconnect();
|
|
8686
|
-
}
|
|
8992
|
+
const { connectionString, vendor, ...operation } = input;
|
|
8993
|
+
return withEphemeralRelationalToolSet(
|
|
8994
|
+
{ vendor, connectionString },
|
|
8995
|
+
async (toolSet) => replaceConnectionFailureMessage(
|
|
8996
|
+
await toolSet.select.invoke(operation),
|
|
8997
|
+
SELECT_CONNECTION_FAILURE,
|
|
8998
|
+
"Failed to execute SELECT query. Please verify your input and database connection."
|
|
8999
|
+
)
|
|
9000
|
+
);
|
|
8687
9001
|
}).build();
|
|
8688
|
-
var
|
|
9002
|
+
var updateValueSchema = z.union([
|
|
9003
|
+
z.string().describe("String value"),
|
|
9004
|
+
z.number().describe("Numeric value"),
|
|
9005
|
+
z.boolean().describe("Boolean value"),
|
|
9006
|
+
z.null().describe("Null value")
|
|
9007
|
+
]).describe("Scalar value for an UPDATE column");
|
|
9008
|
+
var updateDataSchema = z.record(
|
|
9009
|
+
z.string().min(1, "Column name must not be empty").describe("Column name"),
|
|
9010
|
+
updateValueSchema
|
|
9011
|
+
).superRefine((value, ctx) => {
|
|
9012
|
+
if (Object.keys(value).length === 0) {
|
|
9013
|
+
ctx.addIssue({
|
|
9014
|
+
code: z.ZodIssueCode.custom,
|
|
9015
|
+
message: "Update data must not be empty"
|
|
9016
|
+
});
|
|
9017
|
+
}
|
|
9018
|
+
}).describe("Object containing column-value pairs to update");
|
|
9019
|
+
var updateWhereOperatorSchema = z.enum([
|
|
8689
9020
|
"eq",
|
|
8690
9021
|
"ne",
|
|
8691
9022
|
"gt",
|
|
@@ -8698,9 +9029,9 @@ var deleteWhereOperatorSchema = z.enum([
|
|
|
8698
9029
|
"isNull",
|
|
8699
9030
|
"isNotNull"
|
|
8700
9031
|
]);
|
|
8701
|
-
var
|
|
9032
|
+
var updateWhereConditionSchema = z.object({
|
|
8702
9033
|
column: z.string().min(1, "Column name must not be empty").describe("Column name to filter on"),
|
|
8703
|
-
operator:
|
|
9034
|
+
operator: updateWhereOperatorSchema.describe("Comparison operator"),
|
|
8704
9035
|
value: z.union([
|
|
8705
9036
|
z.string(),
|
|
8706
9037
|
z.number(),
|
|
@@ -8768,26 +9099,30 @@ var deleteWhereConditionSchema = z.object({
|
|
|
8768
9099
|
});
|
|
8769
9100
|
}
|
|
8770
9101
|
});
|
|
8771
|
-
var
|
|
8772
|
-
column: z.string().min(1, "
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
9102
|
+
var updateOptimisticLockSchema = z.object({
|
|
9103
|
+
column: z.string().min(1, "Optimistic lock column must not be empty").describe("Version or lock column name"),
|
|
9104
|
+
expectedValue: z.union([
|
|
9105
|
+
z.string().describe("Expected string version value"),
|
|
9106
|
+
z.number().describe("Expected numeric version value")
|
|
9107
|
+
]).describe("Expected current value for the lock column")
|
|
9108
|
+
}).describe("Optional optimistic lock condition");
|
|
9109
|
+
var updateBatchOperationSchema = z.object({
|
|
9110
|
+
data: updateDataSchema.describe("Columns and values to update"),
|
|
9111
|
+
where: z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
9112
|
+
allowFullTableUpdate: z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions for this operation"),
|
|
9113
|
+
optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition for this operation")
|
|
8780
9114
|
}).superRefine((value, ctx) => {
|
|
8781
9115
|
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
8782
|
-
|
|
9116
|
+
const hasOptimisticLock = !!value.optimisticLock;
|
|
9117
|
+
if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
|
|
8783
9118
|
ctx.addIssue({
|
|
8784
9119
|
code: z.ZodIssueCode.custom,
|
|
8785
9120
|
path: ["where"],
|
|
8786
|
-
message: "WHERE conditions are required unless
|
|
9121
|
+
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8787
9122
|
});
|
|
8788
9123
|
}
|
|
8789
9124
|
});
|
|
8790
|
-
var
|
|
9125
|
+
var updateBatchOptionsSchema = z.object({
|
|
8791
9126
|
enabled: z.boolean().optional().default(true).describe("Enable batched processing for operations[] payloads"),
|
|
8792
9127
|
batchSize: z.number().int().positive().max(5e3).optional().default(100).describe("Operations per batch chunk"),
|
|
8793
9128
|
continueOnError: z.boolean().optional().default(true).describe("Continue processing remaining chunks when one chunk fails"),
|
|
@@ -8795,89 +9130,108 @@ var deleteBatchOptionsSchema = z.object({
|
|
|
8795
9130
|
retryDelayMs: z.number().int().min(0).max(6e4).optional().default(0).describe("Delay between retry attempts in milliseconds"),
|
|
8796
9131
|
benchmark: z.boolean().optional().default(false).describe("Run synthetic benchmark metadata comparing individual vs batched execution callbacks")
|
|
8797
9132
|
});
|
|
8798
|
-
var
|
|
9133
|
+
var relationalUpdateSchema = z.object({
|
|
8799
9134
|
table: z.string().min(1, "Table name is required").regex(
|
|
8800
9135
|
VALID_QUALIFIED_IDENTIFIER_PATTERN,
|
|
8801
9136
|
"Table name contains invalid characters. Only alphanumeric characters, underscores, and dots (for schema qualification) are allowed."
|
|
8802
|
-
).describe("Table name to
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
operations: z.array(
|
|
8808
|
-
batch:
|
|
9137
|
+
).describe("Table name to update (schema-qualified names supported, e.g. public.users)"),
|
|
9138
|
+
data: updateDataSchema.optional().describe("Columns and values to update"),
|
|
9139
|
+
where: z.array(updateWhereConditionSchema).optional().describe("WHERE conditions (combined with AND)"),
|
|
9140
|
+
allowFullTableUpdate: z.boolean().default(false).describe("Explicitly allow UPDATE without WHERE conditions"),
|
|
9141
|
+
optimisticLock: updateOptimisticLockSchema.optional().describe("Optional optimistic locking condition"),
|
|
9142
|
+
operations: z.array(updateBatchOperationSchema).min(1, "operations must contain at least one batch item").optional().describe("Batch update operation list"),
|
|
9143
|
+
batch: updateBatchOptionsSchema.optional().describe("Optional batch execution controls for operations payloads"),
|
|
8809
9144
|
vendor: z.enum(["postgresql", "mysql", "sqlite"]).describe("Database vendor"),
|
|
8810
9145
|
connectionString: z.string().min(1, "Database connection string is required").describe("Database connection string")
|
|
8811
9146
|
}).superRefine((value, ctx) => {
|
|
8812
9147
|
const hasOperations = (value.operations?.length ?? 0) > 0;
|
|
8813
|
-
if (hasOperations &&
|
|
9148
|
+
if (hasOperations && value.data) {
|
|
8814
9149
|
ctx.addIssue({
|
|
8815
9150
|
code: z.ZodIssueCode.custom,
|
|
8816
|
-
path: ["
|
|
8817
|
-
message: "
|
|
9151
|
+
path: ["data"],
|
|
9152
|
+
message: "data cannot be provided when operations[] is used"
|
|
8818
9153
|
});
|
|
8819
9154
|
}
|
|
8820
|
-
if (hasOperations && value.
|
|
9155
|
+
if (!hasOperations && !value.data) {
|
|
8821
9156
|
ctx.addIssue({
|
|
8822
9157
|
code: z.ZodIssueCode.custom,
|
|
8823
|
-
path: ["
|
|
8824
|
-
message: "
|
|
9158
|
+
path: ["data"],
|
|
9159
|
+
message: "data is required when operations[] is not provided"
|
|
8825
9160
|
});
|
|
8826
9161
|
}
|
|
8827
|
-
if (hasOperations && value.
|
|
9162
|
+
if (hasOperations && (value.where?.length ?? 0) > 0) {
|
|
8828
9163
|
ctx.addIssue({
|
|
8829
9164
|
code: z.ZodIssueCode.custom,
|
|
8830
|
-
path: ["
|
|
8831
|
-
message: "
|
|
9165
|
+
path: ["where"],
|
|
9166
|
+
message: "where cannot be provided when operations[] is used"
|
|
8832
9167
|
});
|
|
8833
9168
|
}
|
|
8834
|
-
if (hasOperations && value.
|
|
9169
|
+
if (hasOperations && value.optimisticLock) {
|
|
8835
9170
|
ctx.addIssue({
|
|
8836
9171
|
code: z.ZodIssueCode.custom,
|
|
8837
|
-
path: ["
|
|
8838
|
-
message: "
|
|
9172
|
+
path: ["optimisticLock"],
|
|
9173
|
+
message: "optimisticLock cannot be provided when operations[] is used"
|
|
9174
|
+
});
|
|
9175
|
+
}
|
|
9176
|
+
if (hasOperations && value.allowFullTableUpdate) {
|
|
9177
|
+
ctx.addIssue({
|
|
9178
|
+
code: z.ZodIssueCode.custom,
|
|
9179
|
+
path: ["allowFullTableUpdate"],
|
|
9180
|
+
message: "allowFullTableUpdate cannot be provided when operations[] is used"
|
|
8839
9181
|
});
|
|
8840
9182
|
}
|
|
8841
9183
|
if (hasOperations) {
|
|
8842
9184
|
return;
|
|
8843
9185
|
}
|
|
8844
9186
|
const hasWhere = (value.where?.length ?? 0) > 0;
|
|
8845
|
-
|
|
9187
|
+
const hasOptimisticLock = !!value.optimisticLock;
|
|
9188
|
+
if (!value.allowFullTableUpdate && !hasWhere && !hasOptimisticLock) {
|
|
8846
9189
|
ctx.addIssue({
|
|
8847
9190
|
code: z.ZodIssueCode.custom,
|
|
8848
9191
|
path: ["where"],
|
|
8849
|
-
message: "WHERE conditions are required unless
|
|
9192
|
+
message: "WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8850
9193
|
});
|
|
8851
9194
|
}
|
|
8852
9195
|
});
|
|
8853
9196
|
|
|
8854
|
-
// src/data/relational/tools/relational-
|
|
8855
|
-
var
|
|
9197
|
+
// src/data/relational/tools/relational-update/error-utils.ts
|
|
9198
|
+
var SAFE_UPDATE_VALIDATION_PATTERNS = [
|
|
8856
9199
|
"must not be empty",
|
|
8857
9200
|
"contains invalid characters",
|
|
8858
|
-
"
|
|
8859
|
-
"
|
|
8860
|
-
"
|
|
9201
|
+
"Update data must be an object",
|
|
9202
|
+
"Update data must not be empty",
|
|
9203
|
+
"must not be undefined",
|
|
9204
|
+
"WHERE conditions are required for UPDATE queries",
|
|
8861
9205
|
"null is only allowed with isNull/isNotNull operators",
|
|
8862
|
-
"operator requires a value",
|
|
8863
9206
|
"operator requires a string or number value",
|
|
8864
|
-
"operator requires a string value",
|
|
8865
9207
|
"operator requires a non-empty array value",
|
|
8866
|
-
"
|
|
9208
|
+
"operator requires a string value",
|
|
9209
|
+
"must not include value",
|
|
9210
|
+
"Optimistic lock expectedValue must not be empty",
|
|
9211
|
+
"optimistic lock check failed",
|
|
9212
|
+
"WHERE conditions are required unless allowFullTableUpdate is true"
|
|
8867
9213
|
];
|
|
8868
9214
|
var CONSTRAINT_VIOLATION_PATTERNS3 = [
|
|
8869
9215
|
{
|
|
8870
|
-
pattern: /(
|
|
8871
|
-
message: "
|
|
9216
|
+
pattern: /(unique constraint|duplicate key|duplicate entry)/i,
|
|
9217
|
+
message: "Update failed: unique constraint violation."
|
|
9218
|
+
},
|
|
9219
|
+
{
|
|
9220
|
+
pattern: /(foreign key constraint|violates foreign key constraint)/i,
|
|
9221
|
+
message: "Update failed: foreign key constraint violation."
|
|
9222
|
+
},
|
|
9223
|
+
{
|
|
9224
|
+
pattern: /(not null constraint|cannot be null)/i,
|
|
9225
|
+
message: "Update failed: NOT NULL constraint violation."
|
|
8872
9226
|
}
|
|
8873
9227
|
];
|
|
8874
|
-
function
|
|
9228
|
+
function isSafeUpdateValidationError(error) {
|
|
8875
9229
|
if (!(error instanceof Error)) {
|
|
8876
9230
|
return false;
|
|
8877
9231
|
}
|
|
8878
|
-
return
|
|
9232
|
+
return SAFE_UPDATE_VALIDATION_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
8879
9233
|
}
|
|
8880
|
-
function
|
|
9234
|
+
function getUpdateConstraintViolationMessage(error) {
|
|
8881
9235
|
if (!(error instanceof Error)) {
|
|
8882
9236
|
return null;
|
|
8883
9237
|
}
|
|
@@ -8887,21 +9241,18 @@ function getDeleteConstraintViolationMessage(error, cascade) {
|
|
|
8887
9241
|
}
|
|
8888
9242
|
for (const entry of CONSTRAINT_VIOLATION_PATTERNS3) {
|
|
8889
9243
|
if (messages.some((msg) => entry.pattern.test(msg))) {
|
|
8890
|
-
if (cascade) {
|
|
8891
|
-
return `${entry.message} Verify database-level ON DELETE CASCADE is configured for related foreign keys.`;
|
|
8892
|
-
}
|
|
8893
9244
|
return entry.message;
|
|
8894
9245
|
}
|
|
8895
9246
|
}
|
|
8896
9247
|
return null;
|
|
8897
9248
|
}
|
|
8898
|
-
function
|
|
9249
|
+
function isSafeUpdateError(error) {
|
|
8899
9250
|
if (!(error instanceof Error)) {
|
|
8900
9251
|
return false;
|
|
8901
9252
|
}
|
|
8902
|
-
return
|
|
9253
|
+
return isSafeUpdateValidationError(error) || getUpdateConstraintViolationMessage(error) !== null;
|
|
8903
9254
|
}
|
|
8904
|
-
var
|
|
9255
|
+
var updateExecutorLogger = createLogger("agentforge:tools:data:relational:update");
|
|
8905
9256
|
function toNumber3(value) {
|
|
8906
9257
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8907
9258
|
}
|
|
@@ -8938,71 +9289,73 @@ function resolveBatchOptions3(batch) {
|
|
|
8938
9289
|
benchmark: batch.benchmark ?? false
|
|
8939
9290
|
};
|
|
8940
9291
|
}
|
|
8941
|
-
function
|
|
9292
|
+
function toSingleUpdateOperation(input) {
|
|
9293
|
+
if (!input.data) {
|
|
9294
|
+
throw new Error("UPDATE data is required when operations[] is not provided.");
|
|
9295
|
+
}
|
|
8942
9296
|
return {
|
|
9297
|
+
data: input.data,
|
|
8943
9298
|
where: input.where,
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
softDelete: input.softDelete
|
|
9299
|
+
allowFullTableUpdate: input.allowFullTableUpdate,
|
|
9300
|
+
optimisticLock: input.optimisticLock
|
|
8947
9301
|
};
|
|
8948
9302
|
}
|
|
8949
9303
|
|
|
8950
|
-
// src/data/relational/tools/relational-
|
|
8951
|
-
async function
|
|
8952
|
-
const built =
|
|
9304
|
+
// src/data/relational/tools/relational-update/executor-single.ts
|
|
9305
|
+
async function executeSingleUpdate(executor, input, operation, context) {
|
|
9306
|
+
const built = buildUpdateQuery({
|
|
8953
9307
|
table: input.table,
|
|
9308
|
+
data: operation.data,
|
|
8954
9309
|
where: operation.where,
|
|
8955
|
-
|
|
8956
|
-
|
|
9310
|
+
allowFullTableUpdate: operation.allowFullTableUpdate,
|
|
9311
|
+
optimisticLock: operation.optimisticLock,
|
|
8957
9312
|
vendor: input.vendor
|
|
8958
9313
|
});
|
|
8959
|
-
const
|
|
8960
|
-
const rawResult = await
|
|
9314
|
+
const session = context?.transaction ?? executor;
|
|
9315
|
+
const rawResult = await session.execute(built.query);
|
|
8961
9316
|
const rowCount = normalizeAffectedRows2(rawResult);
|
|
9317
|
+
if (built.usesOptimisticLock && rowCount === 0) {
|
|
9318
|
+
throw new Error("Update failed: optimistic lock check failed.");
|
|
9319
|
+
}
|
|
8962
9320
|
return {
|
|
8963
9321
|
rowCount,
|
|
8964
|
-
executionTime: 0
|
|
8965
|
-
softDeleted: built.usesSoftDelete
|
|
9322
|
+
executionTime: 0
|
|
8966
9323
|
};
|
|
8967
9324
|
}
|
|
8968
9325
|
|
|
8969
|
-
// src/data/relational/tools/relational-
|
|
8970
|
-
async function
|
|
9326
|
+
// src/data/relational/tools/relational-update/executor-batch.ts
|
|
9327
|
+
async function executeUpdateInBatchMode(executor, input, context, options) {
|
|
8971
9328
|
const batchResult = await executeBatchedTask(
|
|
8972
9329
|
{
|
|
8973
|
-
operation: "
|
|
9330
|
+
operation: "update",
|
|
8974
9331
|
items: input.operations,
|
|
8975
9332
|
executeBatch: async (operations, batchIndex) => {
|
|
8976
9333
|
let rowCount2 = 0;
|
|
8977
9334
|
let successfulItems = 0;
|
|
8978
9335
|
let failedItems = 0;
|
|
8979
|
-
let softDeletedCount = 0;
|
|
8980
9336
|
const failures = [];
|
|
8981
9337
|
for (const operation of operations) {
|
|
8982
9338
|
try {
|
|
8983
|
-
const result = await
|
|
8984
|
-
|
|
9339
|
+
const result = await executeSingleUpdate(
|
|
9340
|
+
executor,
|
|
8985
9341
|
input,
|
|
8986
9342
|
{
|
|
9343
|
+
data: operation.data,
|
|
8987
9344
|
where: operation.where,
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
softDelete: operation.softDelete
|
|
9345
|
+
allowFullTableUpdate: operation.allowFullTableUpdate,
|
|
9346
|
+
optimisticLock: operation.optimisticLock
|
|
8991
9347
|
},
|
|
8992
9348
|
context
|
|
8993
9349
|
);
|
|
8994
9350
|
rowCount2 += result.rowCount;
|
|
8995
9351
|
successfulItems += 1;
|
|
8996
|
-
if (result.softDeleted) {
|
|
8997
|
-
softDeletedCount += 1;
|
|
8998
|
-
}
|
|
8999
9352
|
} catch (error) {
|
|
9000
9353
|
if (!options.continueOnError) {
|
|
9001
9354
|
throw error;
|
|
9002
9355
|
}
|
|
9003
9356
|
failedItems += 1;
|
|
9004
9357
|
failures.push({
|
|
9005
|
-
operation: "
|
|
9358
|
+
operation: "update",
|
|
9006
9359
|
batchIndex,
|
|
9007
9360
|
batchSize: operations.length,
|
|
9008
9361
|
attempts: 1,
|
|
@@ -9014,7 +9367,6 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
|
|
|
9014
9367
|
rowCount: rowCount2,
|
|
9015
9368
|
successfulItems,
|
|
9016
9369
|
failedItems,
|
|
9017
|
-
softDeletedCount,
|
|
9018
9370
|
failures
|
|
9019
9371
|
};
|
|
9020
9372
|
},
|
|
@@ -9026,7 +9378,7 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
|
|
|
9026
9378
|
maxRetries: options.maxRetries,
|
|
9027
9379
|
retryDelayMs: options.retryDelayMs,
|
|
9028
9380
|
onProgress: (progress) => {
|
|
9029
|
-
|
|
9381
|
+
updateExecutorLogger.debug("UPDATE batch progress", {
|
|
9030
9382
|
vendor: input.vendor,
|
|
9031
9383
|
table: input.table,
|
|
9032
9384
|
...progress
|
|
@@ -9035,19 +9387,15 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
|
|
|
9035
9387
|
}
|
|
9036
9388
|
);
|
|
9037
9389
|
const rowCount = batchResult.results.reduce((total, result) => total + result.rowCount, 0);
|
|
9038
|
-
const softDeleted = batchResult.results.some((result) => result.softDeletedCount > 0);
|
|
9039
9390
|
const operationFailures = batchResult.results.flatMap((result) => result.failures);
|
|
9040
9391
|
let benchmark;
|
|
9041
9392
|
if (options.benchmark) {
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
batchSize: options.batchSize
|
|
9049
|
-
}
|
|
9050
|
-
);
|
|
9393
|
+
updateExecutorLogger.warn("UPDATE batch benchmark enabled. Synthetic benchmark callbacks are side-effect free and do not execute SQL.", {
|
|
9394
|
+
vendor: input.vendor,
|
|
9395
|
+
table: input.table,
|
|
9396
|
+
operationCount: input.operations.length,
|
|
9397
|
+
batchSize: options.batchSize
|
|
9398
|
+
});
|
|
9051
9399
|
benchmark = await benchmarkBatchExecution({
|
|
9052
9400
|
items: input.operations,
|
|
9053
9401
|
batchSize: options.batchSize,
|
|
@@ -9060,7 +9408,6 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
|
|
|
9060
9408
|
return {
|
|
9061
9409
|
rowCount,
|
|
9062
9410
|
executionTime: batchResult.executionTime,
|
|
9063
|
-
softDeleted,
|
|
9064
9411
|
batch: {
|
|
9065
9412
|
enabled: true,
|
|
9066
9413
|
batchSize: options.batchSize,
|
|
@@ -9077,35 +9424,32 @@ async function executeDeleteInBatchMode(manager, input, context, options) {
|
|
|
9077
9424
|
};
|
|
9078
9425
|
}
|
|
9079
9426
|
|
|
9080
|
-
// src/data/relational/tools/relational-
|
|
9081
|
-
async function
|
|
9427
|
+
// src/data/relational/tools/relational-update/executor.ts
|
|
9428
|
+
async function executeUpdate(executor, input, context) {
|
|
9082
9429
|
const startTime = Date.now();
|
|
9083
|
-
|
|
9430
|
+
updateExecutorLogger.debug("Building UPDATE query", {
|
|
9084
9431
|
vendor: input.vendor,
|
|
9085
9432
|
table: input.table,
|
|
9086
9433
|
hasWhere: !!input.where?.length,
|
|
9087
|
-
...input.
|
|
9088
|
-
|
|
9089
|
-
softDelete: !!input.softDelete,
|
|
9434
|
+
...input.allowFullTableUpdate !== void 0 ? { allowFullTableUpdate: input.allowFullTableUpdate } : {},
|
|
9435
|
+
hasOptimisticLock: !!input.optimisticLock,
|
|
9090
9436
|
operationCount: input.operations?.length ?? 0,
|
|
9091
9437
|
batchModeEnabled: !!input.batch?.enabled
|
|
9092
9438
|
});
|
|
9093
9439
|
try {
|
|
9094
9440
|
const batchOptions = resolveBatchOptions3(input.batch);
|
|
9095
|
-
const result = input.operations && batchOptions ? await
|
|
9096
|
-
|
|
9441
|
+
const result = input.operations && batchOptions ? await executeUpdateInBatchMode(
|
|
9442
|
+
executor,
|
|
9097
9443
|
input,
|
|
9098
9444
|
context,
|
|
9099
9445
|
batchOptions
|
|
9100
|
-
) : await
|
|
9446
|
+
) : await executeSingleUpdate(executor, input, toSingleUpdateOperation(input), context);
|
|
9101
9447
|
const executionTime = Date.now() - startTime;
|
|
9102
|
-
|
|
9448
|
+
updateExecutorLogger.debug("UPDATE query executed successfully", {
|
|
9103
9449
|
vendor: input.vendor,
|
|
9104
9450
|
table: input.table,
|
|
9105
9451
|
rowCount: result.rowCount,
|
|
9106
9452
|
executionTime,
|
|
9107
|
-
softDelete: result.softDeleted,
|
|
9108
|
-
...input.cascade !== void 0 ? { cascade: input.cascade } : {},
|
|
9109
9453
|
batchMode: !!result.batch,
|
|
9110
9454
|
partialSuccess: result.batch?.partialSuccess ?? false
|
|
9111
9455
|
});
|
|
@@ -9115,55 +9459,80 @@ async function executeDelete(manager, input, context) {
|
|
|
9115
9459
|
};
|
|
9116
9460
|
} catch (error) {
|
|
9117
9461
|
const executionTime = Date.now() - startTime;
|
|
9118
|
-
|
|
9462
|
+
updateExecutorLogger.error("UPDATE query execution failed", {
|
|
9119
9463
|
vendor: input.vendor,
|
|
9120
9464
|
table: input.table,
|
|
9121
9465
|
error: error instanceof Error ? error.message : String(error),
|
|
9122
|
-
executionTime
|
|
9123
|
-
...input.cascade !== void 0 ? { cascade: input.cascade } : {},
|
|
9124
|
-
softDelete: !!input.softDelete
|
|
9466
|
+
executionTime
|
|
9125
9467
|
});
|
|
9126
|
-
const constraintMessage =
|
|
9468
|
+
const constraintMessage = getUpdateConstraintViolationMessage(error);
|
|
9127
9469
|
if (constraintMessage) {
|
|
9128
9470
|
throw new Error(constraintMessage, { cause: error });
|
|
9129
9471
|
}
|
|
9130
|
-
if (
|
|
9472
|
+
if (isSafeUpdateValidationError(error)) {
|
|
9131
9473
|
throw error;
|
|
9132
9474
|
}
|
|
9133
|
-
throw new Error("
|
|
9475
|
+
throw new Error("UPDATE query failed. See logs for details.", { cause: error });
|
|
9134
9476
|
}
|
|
9135
9477
|
}
|
|
9136
9478
|
|
|
9137
|
-
// src/data/relational/tools/relational-
|
|
9138
|
-
|
|
9139
|
-
|
|
9479
|
+
// src/data/relational/tools/relational-update/index.ts
|
|
9480
|
+
function toUpdateErrorResponse(error) {
|
|
9481
|
+
const errorMessage = isSafeUpdateError(error) ? error.message : "Failed to execute UPDATE query. Please verify your input and database connection.";
|
|
9482
|
+
return {
|
|
9483
|
+
success: false,
|
|
9484
|
+
error: errorMessage,
|
|
9485
|
+
rowCount: 0
|
|
9486
|
+
};
|
|
9487
|
+
}
|
|
9488
|
+
async function invokeRelationalUpdate(execution, input) {
|
|
9489
|
+
try {
|
|
9490
|
+
const result = await executeUpdate(
|
|
9491
|
+
execution.executor,
|
|
9492
|
+
{ ...input, vendor: execution.vendor },
|
|
9493
|
+
execution.transaction ? { transaction: execution.transaction } : void 0
|
|
9494
|
+
);
|
|
9495
|
+
return {
|
|
9496
|
+
success: true,
|
|
9497
|
+
rowCount: result.rowCount,
|
|
9498
|
+
executionTime: result.executionTime,
|
|
9499
|
+
batch: result.batch
|
|
9500
|
+
};
|
|
9501
|
+
} catch (error) {
|
|
9502
|
+
return toUpdateErrorResponse(error);
|
|
9503
|
+
}
|
|
9504
|
+
}
|
|
9505
|
+
var relationalUpdate = toolBuilder().name("relational-update").displayName("Relational UPDATE").description("Execute type-safe UPDATE queries with single and batched operation support").category(ToolCategory.DATABASE).tags(["database", "sql", "update", "postgresql", "mysql", "sqlite"]).schema(relationalUpdateSchema).example({
|
|
9506
|
+
description: "Update one row by condition",
|
|
9140
9507
|
input: {
|
|
9141
9508
|
table: "users",
|
|
9509
|
+
data: { status: "inactive" },
|
|
9142
9510
|
where: [{ column: "id", operator: "eq", value: 123 }],
|
|
9143
9511
|
vendor: "postgresql",
|
|
9144
9512
|
connectionString: "postgresql://localhost/mydb"
|
|
9145
9513
|
}
|
|
9146
9514
|
}).example({
|
|
9147
|
-
description: "
|
|
9515
|
+
description: "Optimistic lock update",
|
|
9148
9516
|
input: {
|
|
9149
9517
|
table: "users",
|
|
9150
|
-
|
|
9151
|
-
|
|
9518
|
+
data: { status: "active" },
|
|
9519
|
+
where: [{ column: "id", operator: "eq", value: 123 }],
|
|
9520
|
+
optimisticLock: { column: "version", expectedValue: 5 },
|
|
9152
9521
|
vendor: "sqlite",
|
|
9153
9522
|
connectionString: "data.db"
|
|
9154
9523
|
}
|
|
9155
9524
|
}).example({
|
|
9156
|
-
description: "Batch
|
|
9525
|
+
description: "Batch update operations",
|
|
9157
9526
|
input: {
|
|
9158
9527
|
table: "users",
|
|
9159
9528
|
operations: [
|
|
9160
9529
|
{
|
|
9161
|
-
|
|
9162
|
-
|
|
9530
|
+
data: { status: "inactive" },
|
|
9531
|
+
where: [{ column: "id", operator: "eq", value: 1 }]
|
|
9163
9532
|
},
|
|
9164
9533
|
{
|
|
9165
|
-
|
|
9166
|
-
|
|
9534
|
+
data: { status: "inactive" },
|
|
9535
|
+
where: [{ column: "id", operator: "eq", value: 2 }]
|
|
9167
9536
|
}
|
|
9168
9537
|
],
|
|
9169
9538
|
batch: {
|
|
@@ -9174,141 +9543,503 @@ var relationalDelete = toolBuilder().name("relational-delete").displayName("Rela
|
|
|
9174
9543
|
connectionString: "postgresql://localhost/mydb"
|
|
9175
9544
|
}
|
|
9176
9545
|
}).implement(async (input) => {
|
|
9177
|
-
const
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
executionTime: result.executionTime,
|
|
9188
|
-
softDeleted: result.softDeleted,
|
|
9189
|
-
batch: result.batch
|
|
9190
|
-
};
|
|
9191
|
-
} catch (error) {
|
|
9192
|
-
let errorMessage = "Failed to execute DELETE query. Please verify your input and database connection.";
|
|
9193
|
-
if (isSafeDeleteError(error)) {
|
|
9194
|
-
errorMessage = error.message;
|
|
9195
|
-
}
|
|
9196
|
-
return {
|
|
9197
|
-
success: false,
|
|
9198
|
-
error: errorMessage,
|
|
9199
|
-
rowCount: 0,
|
|
9200
|
-
softDeleted: false
|
|
9201
|
-
};
|
|
9202
|
-
} finally {
|
|
9203
|
-
await manager.disconnect();
|
|
9204
|
-
}
|
|
9546
|
+
const { connectionString, vendor, ...operation } = input;
|
|
9547
|
+
return withEphemeralRelationalMutationToolSet(
|
|
9548
|
+
{ vendor, connectionString },
|
|
9549
|
+
async (toolSet) => replaceMutationConnectionFailure(
|
|
9550
|
+
await toolSet.update.invoke(operation),
|
|
9551
|
+
UPDATE_CONNECTION_FAILURE,
|
|
9552
|
+
"Failed to execute UPDATE query. Please verify your input and database connection."
|
|
9553
|
+
),
|
|
9554
|
+
toUpdateErrorResponse
|
|
9555
|
+
);
|
|
9205
9556
|
}).build();
|
|
9206
9557
|
|
|
9207
|
-
// src/data/relational/
|
|
9208
|
-
var
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9558
|
+
// src/data/relational/tool-set.ts
|
|
9559
|
+
var DEFAULT_SCHEMA_CACHE_TTL_MS = 6e4;
|
|
9560
|
+
var TOOL_SET_SCHEMA_CACHE_KEY = "relational-tool-set";
|
|
9561
|
+
var PREFIX_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
9562
|
+
var ROLLBACK_ONLY_MESSAGE = "Transaction is rollback-only; no database work was executed.";
|
|
9563
|
+
var logger18 = createLogger("agentforge:tools:data:relational:tool-set");
|
|
9564
|
+
var RelationalToolSetConfigurationError = class extends Error {
|
|
9565
|
+
name = "RelationalToolSetConfigurationError";
|
|
9566
|
+
};
|
|
9567
|
+
var RelationalToolSetDisposedError = class extends Error {
|
|
9568
|
+
name = "RelationalToolSetDisposedError";
|
|
9569
|
+
constructor() {
|
|
9570
|
+
super("The Relational Tool Set is disposing or has been disposed.");
|
|
9571
|
+
}
|
|
9572
|
+
};
|
|
9573
|
+
var RelationalTransactionError = class extends Error {
|
|
9574
|
+
constructor(message, code) {
|
|
9575
|
+
super(message);
|
|
9576
|
+
this.code = code;
|
|
9577
|
+
}
|
|
9578
|
+
name = "RelationalTransactionError";
|
|
9579
|
+
};
|
|
9580
|
+
function isFailedOrPartialResult(result) {
|
|
9581
|
+
if (!result || typeof result !== "object") {
|
|
9215
9582
|
return false;
|
|
9216
9583
|
}
|
|
9217
|
-
|
|
9218
|
-
|
|
9584
|
+
const response = result;
|
|
9585
|
+
return response.success === false || response.batch?.partialSuccess === true || (response.batch?.failedItems ?? 0) > 0;
|
|
9586
|
+
}
|
|
9587
|
+
var TransactionToolScope = class {
|
|
9588
|
+
constructor(execution) {
|
|
9589
|
+
this.execution = execution;
|
|
9590
|
+
}
|
|
9591
|
+
active = true;
|
|
9592
|
+
rollbackOnly = false;
|
|
9593
|
+
tail = Promise.resolve();
|
|
9594
|
+
run(operation, failure) {
|
|
9595
|
+
if (!this.active) {
|
|
9596
|
+
return Promise.reject(
|
|
9597
|
+
new RelationalTransactionError(
|
|
9598
|
+
"Transaction-scoped Tools can no longer be used because their callback has settled.",
|
|
9599
|
+
"SCOPE_EXPIRED"
|
|
9600
|
+
)
|
|
9601
|
+
);
|
|
9602
|
+
}
|
|
9603
|
+
const invocation = this.tail.then(async () => {
|
|
9604
|
+
if (this.rollbackOnly) {
|
|
9605
|
+
return failure(ROLLBACK_ONLY_MESSAGE);
|
|
9606
|
+
}
|
|
9607
|
+
const result = await operation(this.execution);
|
|
9608
|
+
if (isFailedOrPartialResult(result)) {
|
|
9609
|
+
this.rollbackOnly = true;
|
|
9610
|
+
}
|
|
9611
|
+
return result;
|
|
9612
|
+
});
|
|
9613
|
+
this.tail = invocation.then(
|
|
9614
|
+
() => void 0,
|
|
9615
|
+
() => void 0
|
|
9616
|
+
);
|
|
9617
|
+
return invocation;
|
|
9618
|
+
}
|
|
9619
|
+
async settle() {
|
|
9620
|
+
this.active = false;
|
|
9621
|
+
await this.tail;
|
|
9622
|
+
}
|
|
9623
|
+
isRollbackOnly() {
|
|
9624
|
+
return this.rollbackOnly;
|
|
9625
|
+
}
|
|
9626
|
+
};
|
|
9627
|
+
function createConfiguredTools(options, execution) {
|
|
9628
|
+
const query = createTool(
|
|
9629
|
+
configuredMetadata(relationalQuery, options.prefix),
|
|
9630
|
+
relationalQuerySchema.omit({ vendor: true, connectionString: true }),
|
|
9631
|
+
(input) => execution(
|
|
9632
|
+
(context) => invokeRelationalQuery(context, input),
|
|
9633
|
+
(error) => ({ success: false, error, rows: [], rowCount: 0 }),
|
|
9634
|
+
QUERY_CONNECTION_FAILURE
|
|
9635
|
+
)
|
|
9636
|
+
);
|
|
9637
|
+
const select = createTool(
|
|
9638
|
+
configuredMetadata(relationalSelect, options.prefix),
|
|
9639
|
+
relationalSelectSchema.omit({ vendor: true, connectionString: true }),
|
|
9640
|
+
(input) => execution(
|
|
9641
|
+
(context) => invokeRelationalSelect(context, input),
|
|
9642
|
+
(error) => ({ success: false, error, rows: [], rowCount: 0 }),
|
|
9643
|
+
SELECT_CONNECTION_FAILURE
|
|
9644
|
+
)
|
|
9645
|
+
);
|
|
9646
|
+
const insert = createTool(
|
|
9647
|
+
configuredMetadata(relationalInsert, options.prefix),
|
|
9648
|
+
relationalInsertSchema.omit({ vendor: true, connectionString: true }),
|
|
9649
|
+
(input) => execution(
|
|
9650
|
+
(context) => invokeRelationalInsert(context, input),
|
|
9651
|
+
(error) => ({
|
|
9652
|
+
success: false,
|
|
9653
|
+
error,
|
|
9654
|
+
rowCount: 0,
|
|
9655
|
+
insertedIds: [],
|
|
9656
|
+
rows: []
|
|
9657
|
+
}),
|
|
9658
|
+
INSERT_CONNECTION_FAILURE
|
|
9659
|
+
)
|
|
9660
|
+
);
|
|
9661
|
+
const update = createTool(
|
|
9662
|
+
configuredMetadata(relationalUpdate, options.prefix),
|
|
9663
|
+
relationalUpdateOperationSchema,
|
|
9664
|
+
(input) => execution(
|
|
9665
|
+
(context) => invokeRelationalUpdate(context, input),
|
|
9666
|
+
(error) => ({ success: false, error, rowCount: 0 }),
|
|
9667
|
+
UPDATE_CONNECTION_FAILURE
|
|
9668
|
+
)
|
|
9669
|
+
);
|
|
9670
|
+
const deleteTool = createTool(
|
|
9671
|
+
configuredMetadata(relationalDelete, options.prefix),
|
|
9672
|
+
relationalDeleteOperationSchema,
|
|
9673
|
+
(input) => execution(
|
|
9674
|
+
(context) => invokeRelationalDelete(context, input),
|
|
9675
|
+
(error) => ({ success: false, error, rowCount: 0, softDeleted: false }),
|
|
9676
|
+
DELETE_CONNECTION_FAILURE
|
|
9677
|
+
)
|
|
9678
|
+
);
|
|
9679
|
+
const getSchema = createTool(
|
|
9680
|
+
configuredMetadata(relationalGetSchema, options.prefix),
|
|
9681
|
+
relationalGetSchemaInputSchema.omit({
|
|
9682
|
+
vendor: true,
|
|
9683
|
+
connectionString: true,
|
|
9684
|
+
database: true,
|
|
9685
|
+
cacheTtlMs: true
|
|
9686
|
+
}),
|
|
9687
|
+
(input) => execution(
|
|
9688
|
+
(context) => invokeRelationalGetSchema(context, {
|
|
9689
|
+
...input,
|
|
9690
|
+
cacheTtlMs: options.schemaCacheTtlMs ?? DEFAULT_SCHEMA_CACHE_TTL_MS
|
|
9691
|
+
}),
|
|
9692
|
+
(error) => ({ success: false, error, schema: null }),
|
|
9693
|
+
"Failed to inspect schema. See logs for details."
|
|
9694
|
+
)
|
|
9219
9695
|
);
|
|
9696
|
+
const tools = [query, select, insert, update, deleteTool, getSchema];
|
|
9697
|
+
return Object.freeze({
|
|
9698
|
+
query,
|
|
9699
|
+
select,
|
|
9700
|
+
insert,
|
|
9701
|
+
update,
|
|
9702
|
+
delete: deleteTool,
|
|
9703
|
+
getSchema,
|
|
9704
|
+
[Symbol.iterator]: () => tools[Symbol.iterator]()
|
|
9705
|
+
});
|
|
9220
9706
|
}
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9707
|
+
function validateConfiguration(config, options) {
|
|
9708
|
+
if (!config || !["postgresql", "mysql", "sqlite"].includes(config.vendor)) {
|
|
9709
|
+
throw new RelationalToolSetConfigurationError(
|
|
9710
|
+
"Database vendor must be postgresql, mysql, or sqlite."
|
|
9711
|
+
);
|
|
9712
|
+
}
|
|
9713
|
+
if (typeof config.connection === "string" && config.connection.trim().length === 0 || typeof config.connection !== "string" && (!config.connection || Array.isArray(config.connection))) {
|
|
9714
|
+
throw new RelationalToolSetConfigurationError(
|
|
9715
|
+
"Database connection must be a non-empty string or configuration object."
|
|
9716
|
+
);
|
|
9717
|
+
}
|
|
9718
|
+
if (config.vendor === "sqlite" && typeof config.connection !== "string" && (typeof config.connection.url !== "string" || config.connection.url.trim().length === 0)) {
|
|
9719
|
+
throw new RelationalToolSetConfigurationError(
|
|
9720
|
+
"SQLite object configuration requires a non-empty url."
|
|
9721
|
+
);
|
|
9722
|
+
}
|
|
9723
|
+
if (options.prefix !== void 0 && !PREFIX_PATTERN.test(options.prefix)) {
|
|
9724
|
+
throw new RelationalToolSetConfigurationError(
|
|
9725
|
+
"Tool name prefix must be non-empty kebab-case starting with a letter."
|
|
9726
|
+
);
|
|
9727
|
+
}
|
|
9728
|
+
if (options.schemaCacheTtlMs !== void 0 && (!Number.isInteger(options.schemaCacheTtlMs) || options.schemaCacheTtlMs < 0)) {
|
|
9729
|
+
throw new RelationalToolSetConfigurationError(
|
|
9730
|
+
"Schema cache TTL must be a non-negative integer."
|
|
9731
|
+
);
|
|
9732
|
+
}
|
|
9733
|
+
const longestName = configuredName("relational-get-schema", options.prefix);
|
|
9734
|
+
if (longestName.length > 50) {
|
|
9735
|
+
throw new RelationalToolSetConfigurationError(
|
|
9736
|
+
"Tool name prefix is too long; configured Tool names must not exceed 50 characters."
|
|
9737
|
+
);
|
|
9738
|
+
}
|
|
9228
9739
|
}
|
|
9229
|
-
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
database: z.string().min(1).optional().describe("Optional logical database name used for cache scoping"),
|
|
9233
|
-
tables: z.array(
|
|
9234
|
-
z.string().regex(
|
|
9235
|
-
VALID_TABLE_FILTER_PATTERN,
|
|
9236
|
-
"Table filter contains invalid characters. Use alphanumeric, underscore, and optional schema qualification."
|
|
9237
|
-
)
|
|
9238
|
-
).optional().describe("Optional table filter list. When omitted, all tables are returned."),
|
|
9239
|
-
cacheTtlMs: z.number().int().min(0).optional().describe("Schema cache TTL in milliseconds (0 disables caching)"),
|
|
9240
|
-
refreshCache: z.boolean().optional().describe("Force cache invalidation before introspecting schema")
|
|
9241
|
-
});
|
|
9242
|
-
var relationalGetSchema = 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(ToolCategory.DATABASE).tags(["database", "schema", "introspection", "postgresql", "mysql", "sqlite"]).schema(relationalGetSchemaInputSchema).example({
|
|
9243
|
-
description: "Inspect all SQLite tables",
|
|
9244
|
-
input: {
|
|
9245
|
-
vendor: "sqlite",
|
|
9246
|
-
connectionString: ":memory:"
|
|
9740
|
+
function cloneImmutableValue(value) {
|
|
9741
|
+
if (Array.isArray(value)) {
|
|
9742
|
+
return Object.freeze(value.map(cloneImmutableValue));
|
|
9247
9743
|
}
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
input: {
|
|
9251
|
-
vendor: "postgresql",
|
|
9252
|
-
connectionString: "postgresql://localhost/app",
|
|
9253
|
-
tables: ["public.users", "public.orders"],
|
|
9254
|
-
cacheTtlMs: 3e4,
|
|
9255
|
-
refreshCache: true
|
|
9744
|
+
if (value instanceof Date) {
|
|
9745
|
+
return new Date(value.getTime());
|
|
9256
9746
|
}
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
)
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9747
|
+
if (value instanceof ArrayBuffer) {
|
|
9748
|
+
return value.slice(0);
|
|
9749
|
+
}
|
|
9750
|
+
if (Buffer.isBuffer(value)) {
|
|
9751
|
+
return Buffer.from(value);
|
|
9752
|
+
}
|
|
9753
|
+
if (ArrayBuffer.isView(value)) {
|
|
9754
|
+
return structuredClone(value);
|
|
9755
|
+
}
|
|
9756
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
9757
|
+
return Object.freeze(
|
|
9758
|
+
Object.fromEntries(
|
|
9759
|
+
Object.entries(value).map(([key, nestedValue]) => [key, cloneImmutableValue(nestedValue)])
|
|
9760
|
+
)
|
|
9761
|
+
);
|
|
9762
|
+
}
|
|
9763
|
+
if (value && typeof value === "object") {
|
|
9764
|
+
throw new RelationalToolSetConfigurationError(
|
|
9765
|
+
"Database configuration contains an unsupported mutable object."
|
|
9766
|
+
);
|
|
9767
|
+
}
|
|
9768
|
+
return value;
|
|
9769
|
+
}
|
|
9770
|
+
function snapshotConfiguration(config) {
|
|
9771
|
+
const connection = cloneImmutableValue(config.connection);
|
|
9772
|
+
return Object.freeze({ vendor: config.vendor, connection });
|
|
9773
|
+
}
|
|
9774
|
+
function configuredName(name, prefix) {
|
|
9775
|
+
return prefix ? `${prefix}-${name}` : name;
|
|
9776
|
+
}
|
|
9777
|
+
function configuredMetadata(tool, prefix) {
|
|
9778
|
+
return {
|
|
9779
|
+
...tool.metadata,
|
|
9780
|
+
name: configuredName(tool.metadata.name, prefix),
|
|
9781
|
+
examples: void 0
|
|
9782
|
+
};
|
|
9783
|
+
}
|
|
9784
|
+
function preserveOperationValidation(legacySchema, input, context) {
|
|
9785
|
+
const result = legacySchema.safeParse({
|
|
9786
|
+
...input,
|
|
9787
|
+
vendor: "sqlite",
|
|
9788
|
+
connectionString: "configured"
|
|
9270
9789
|
});
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9790
|
+
if (!result.success) {
|
|
9791
|
+
for (const issue of result.error.issues) {
|
|
9792
|
+
context.addIssue(issue);
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
var relationalUpdateOperationSchema = relationalUpdateSchema.innerType().omit({ vendor: true, connectionString: true }).superRefine((input, context) => {
|
|
9797
|
+
preserveOperationValidation(relationalUpdateSchema, input, context);
|
|
9798
|
+
});
|
|
9799
|
+
var relationalDeleteOperationSchema = relationalDeleteSchema.innerType().omit({ vendor: true, connectionString: true }).superRefine((input, context) => {
|
|
9800
|
+
preserveOperationValidation(relationalDeleteSchema, input, context);
|
|
9801
|
+
});
|
|
9802
|
+
var RelationalToolSetImplementation = class {
|
|
9803
|
+
constructor(config, options, sharedSchemaCacheKey) {
|
|
9804
|
+
this.config = config;
|
|
9805
|
+
this.sharedSchemaCacheKey = sharedSchemaCacheKey;
|
|
9806
|
+
this.prefix = options.prefix;
|
|
9807
|
+
const configuredTools = createConfiguredTools(
|
|
9808
|
+
options,
|
|
9809
|
+
(operation, failure, failureMessage) => this.run(operation, () => failure(failureMessage))
|
|
9810
|
+
);
|
|
9811
|
+
this.query = configuredTools.query;
|
|
9812
|
+
this.select = configuredTools.select;
|
|
9813
|
+
this.insert = configuredTools.insert;
|
|
9814
|
+
this.update = configuredTools.update;
|
|
9815
|
+
this.delete = configuredTools.delete;
|
|
9816
|
+
this.getSchema = configuredTools.getSchema;
|
|
9817
|
+
this.tools = Object.freeze([
|
|
9818
|
+
this.query,
|
|
9819
|
+
this.select,
|
|
9820
|
+
this.insert,
|
|
9821
|
+
this.update,
|
|
9822
|
+
this.delete,
|
|
9823
|
+
this.getSchema
|
|
9824
|
+
]);
|
|
9825
|
+
}
|
|
9826
|
+
query;
|
|
9827
|
+
select;
|
|
9828
|
+
insert;
|
|
9829
|
+
update;
|
|
9830
|
+
delete;
|
|
9831
|
+
getSchema;
|
|
9832
|
+
tools;
|
|
9833
|
+
schemaCache = new SchemaCache();
|
|
9834
|
+
prefix;
|
|
9835
|
+
transactionContext = new AsyncLocalStorage();
|
|
9836
|
+
sqliteWorkTail = Promise.resolve();
|
|
9837
|
+
manager;
|
|
9838
|
+
initialization;
|
|
9839
|
+
activeWork = 0;
|
|
9840
|
+
state = "open";
|
|
9841
|
+
drain;
|
|
9842
|
+
resolveDrain;
|
|
9843
|
+
disposal;
|
|
9844
|
+
[Symbol.iterator]() {
|
|
9845
|
+
return this.tools[Symbol.iterator]();
|
|
9846
|
+
}
|
|
9847
|
+
async transaction(operation, options) {
|
|
9848
|
+
if (this.transactionContext.getStore()?.active) {
|
|
9849
|
+
throw new RelationalTransactionError(
|
|
9850
|
+
"Nested Relational Tool Set transactions are not supported.",
|
|
9851
|
+
"NESTED_TRANSACTION"
|
|
9852
|
+
);
|
|
9275
9853
|
}
|
|
9276
|
-
|
|
9277
|
-
|
|
9854
|
+
return this.runTransaction((manager) => {
|
|
9855
|
+
const transactionState = { active: true };
|
|
9856
|
+
return this.transactionContext.run(
|
|
9857
|
+
transactionState,
|
|
9858
|
+
() => withTransaction(
|
|
9859
|
+
manager,
|
|
9860
|
+
async (transaction) => {
|
|
9861
|
+
const scope = new TransactionToolScope(this.transactionExecutionFor(transaction));
|
|
9862
|
+
const scopedTools = createConfiguredTools(
|
|
9863
|
+
{ prefix: this.prefix },
|
|
9864
|
+
(scopedOperation, failure) => scope.run(scopedOperation, failure)
|
|
9865
|
+
);
|
|
9866
|
+
let result;
|
|
9867
|
+
try {
|
|
9868
|
+
result = await operation(scopedTools);
|
|
9869
|
+
} finally {
|
|
9870
|
+
try {
|
|
9871
|
+
await scope.settle();
|
|
9872
|
+
} finally {
|
|
9873
|
+
transactionState.active = false;
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
if (scope.isRollbackOnly()) {
|
|
9877
|
+
throw new RelationalTransactionError(
|
|
9878
|
+
"The transaction was rolled back because a scoped Tool failed.",
|
|
9879
|
+
"ROLLBACK_ONLY"
|
|
9880
|
+
);
|
|
9881
|
+
}
|
|
9882
|
+
return result;
|
|
9883
|
+
},
|
|
9884
|
+
options
|
|
9885
|
+
)
|
|
9886
|
+
);
|
|
9278
9887
|
});
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9888
|
+
}
|
|
9889
|
+
refreshSchema() {
|
|
9890
|
+
if (this.state !== "open") {
|
|
9891
|
+
throw new RelationalToolSetDisposedError();
|
|
9892
|
+
}
|
|
9893
|
+
if (this.sharedSchemaCacheKey) {
|
|
9894
|
+
SchemaInspector.clearCache(this.sharedSchemaCacheKey);
|
|
9895
|
+
} else {
|
|
9896
|
+
this.schemaCache.delete(TOOL_SET_SCHEMA_CACHE_KEY);
|
|
9897
|
+
}
|
|
9898
|
+
}
|
|
9899
|
+
dispose() {
|
|
9900
|
+
if (!this.disposal) {
|
|
9901
|
+
this.state = "closing";
|
|
9902
|
+
this.disposal = this.finishDisposal();
|
|
9903
|
+
}
|
|
9904
|
+
return this.disposal;
|
|
9905
|
+
}
|
|
9906
|
+
async run(operation, connectionFailure) {
|
|
9907
|
+
if (this.transactionContext.getStore()?.active) {
|
|
9908
|
+
throw new RelationalTransactionError(
|
|
9909
|
+
"Use the scoped Tools supplied to the transaction callback.",
|
|
9910
|
+
"UNSCOPED_TOOL_IN_TRANSACTION"
|
|
9911
|
+
);
|
|
9912
|
+
}
|
|
9913
|
+
if (this.state !== "open") {
|
|
9914
|
+
throw new RelationalToolSetDisposedError();
|
|
9915
|
+
}
|
|
9916
|
+
this.activeWork += 1;
|
|
9917
|
+
try {
|
|
9918
|
+
let manager;
|
|
9919
|
+
try {
|
|
9920
|
+
manager = await this.ensureConnection();
|
|
9921
|
+
} catch (error) {
|
|
9922
|
+
if (error instanceof MissingPeerDependencyError) {
|
|
9923
|
+
throw error;
|
|
9924
|
+
}
|
|
9925
|
+
return connectionFailure();
|
|
9926
|
+
}
|
|
9927
|
+
const execute = () => operation(this.schemaExecutionFor(manager));
|
|
9928
|
+
return this.config.vendor === "sqlite" ? await this.serializeSqliteWork(execute) : await execute();
|
|
9929
|
+
} finally {
|
|
9930
|
+
this.activeWork -= 1;
|
|
9931
|
+
if (this.activeWork === 0) {
|
|
9932
|
+
this.resolveDrain?.();
|
|
9933
|
+
}
|
|
9934
|
+
}
|
|
9935
|
+
}
|
|
9936
|
+
async runTransaction(operation) {
|
|
9937
|
+
if (this.state !== "open") {
|
|
9938
|
+
throw new RelationalToolSetDisposedError();
|
|
9939
|
+
}
|
|
9940
|
+
this.activeWork += 1;
|
|
9941
|
+
try {
|
|
9942
|
+
let manager;
|
|
9943
|
+
try {
|
|
9944
|
+
manager = await this.ensureConnection();
|
|
9945
|
+
} catch (error) {
|
|
9946
|
+
if (error instanceof MissingPeerDependencyError) {
|
|
9947
|
+
throw error;
|
|
9948
|
+
}
|
|
9949
|
+
throw new RelationalTransactionError(
|
|
9950
|
+
"Failed to start the managed database transaction.",
|
|
9951
|
+
"START_FAILED"
|
|
9952
|
+
);
|
|
9953
|
+
}
|
|
9954
|
+
if (this.config.vendor !== "sqlite") {
|
|
9955
|
+
return await operation(manager);
|
|
9956
|
+
}
|
|
9957
|
+
return await this.serializeSqliteWork(() => operation(manager));
|
|
9958
|
+
} finally {
|
|
9959
|
+
this.activeWork -= 1;
|
|
9960
|
+
if (this.activeWork === 0) {
|
|
9961
|
+
this.resolveDrain?.();
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
}
|
|
9965
|
+
async serializeSqliteWork(operation) {
|
|
9966
|
+
const work = this.sqliteWorkTail.then(operation);
|
|
9967
|
+
this.sqliteWorkTail = work.then(
|
|
9968
|
+
() => void 0,
|
|
9969
|
+
() => void 0
|
|
9288
9970
|
);
|
|
9289
|
-
return
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9971
|
+
return work;
|
|
9972
|
+
}
|
|
9973
|
+
ensureConnection() {
|
|
9974
|
+
if (this.manager?.isConnected()) {
|
|
9975
|
+
return Promise.resolve(this.manager);
|
|
9976
|
+
}
|
|
9977
|
+
if (!this.initialization) {
|
|
9978
|
+
const manager = new ConnectionManager(this.config);
|
|
9979
|
+
this.manager = manager;
|
|
9980
|
+
this.initialization = manager.connect().then(() => manager).catch(async (error) => {
|
|
9981
|
+
this.manager = void 0;
|
|
9982
|
+
this.initialization = void 0;
|
|
9983
|
+
if (!(error instanceof MissingPeerDependencyError)) {
|
|
9984
|
+
logger18.error("Relational Tool Set connection initialization failed", {
|
|
9985
|
+
vendor: this.config.vendor,
|
|
9986
|
+
errorType: error instanceof Error ? error.name : typeof error
|
|
9987
|
+
});
|
|
9988
|
+
}
|
|
9989
|
+
try {
|
|
9990
|
+
await manager.dispose();
|
|
9991
|
+
} catch {
|
|
9992
|
+
}
|
|
9993
|
+
throw error;
|
|
9994
|
+
});
|
|
9995
|
+
}
|
|
9996
|
+
return this.initialization;
|
|
9997
|
+
}
|
|
9998
|
+
schemaExecutionFor(manager) {
|
|
9999
|
+
const execution = { executor: manager, vendor: this.config.vendor };
|
|
10000
|
+
return this.sharedSchemaCacheKey ? { ...execution, schemaCacheKey: this.sharedSchemaCacheKey } : {
|
|
10001
|
+
...execution,
|
|
10002
|
+
schemaCache: this.schemaCache,
|
|
10003
|
+
schemaCacheKey: TOOL_SET_SCHEMA_CACHE_KEY
|
|
9293
10004
|
};
|
|
9294
|
-
}
|
|
9295
|
-
|
|
9296
|
-
vendor: input.vendor,
|
|
9297
|
-
hasTablesFilter: Array.isArray(input.tables),
|
|
9298
|
-
tablesFilterCount: input.tables?.length ?? 0,
|
|
9299
|
-
refreshCache: input.refreshCache ?? false,
|
|
9300
|
-
error: error instanceof Error ? error.message : String(error)
|
|
9301
|
-
});
|
|
9302
|
-
const errorMessage = isSafeGetSchemaValidationError(error) ? error.message : "Failed to inspect schema. See logs for details.";
|
|
10005
|
+
}
|
|
10006
|
+
transactionExecutionFor(transaction) {
|
|
9303
10007
|
return {
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
10008
|
+
executor: transaction,
|
|
10009
|
+
transaction,
|
|
10010
|
+
vendor: this.config.vendor
|
|
9307
10011
|
};
|
|
9308
|
-
} finally {
|
|
9309
|
-
await manager.disconnect();
|
|
9310
10012
|
}
|
|
9311
|
-
|
|
10013
|
+
async finishDisposal() {
|
|
10014
|
+
if (this.activeWork > 0) {
|
|
10015
|
+
this.drain = new Promise((resolve3) => {
|
|
10016
|
+
this.resolveDrain = resolve3;
|
|
10017
|
+
});
|
|
10018
|
+
await this.drain;
|
|
10019
|
+
}
|
|
10020
|
+
this.schemaCache.clear();
|
|
10021
|
+
try {
|
|
10022
|
+
await this.manager?.dispose();
|
|
10023
|
+
} finally {
|
|
10024
|
+
this.state = "closed";
|
|
10025
|
+
this.resolveDrain = void 0;
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
};
|
|
10029
|
+
function createRelationalToolSet(config, options = {}) {
|
|
10030
|
+
validateConfiguration(config, options);
|
|
10031
|
+
return new RelationalToolSetImplementation(snapshotConfiguration(config), { ...options });
|
|
10032
|
+
}
|
|
10033
|
+
function createLegacyRelationalReadToolSet(config, options = {}, schemaCacheKey) {
|
|
10034
|
+
return new RelationalToolSetImplementation(
|
|
10035
|
+
snapshotConfiguration(config),
|
|
10036
|
+
{ ...options },
|
|
10037
|
+
schemaCacheKey
|
|
10038
|
+
);
|
|
10039
|
+
}
|
|
10040
|
+
function createLegacyRelationalMutationToolSet(config) {
|
|
10041
|
+
return new RelationalToolSetImplementation(snapshotConfiguration(config), {});
|
|
10042
|
+
}
|
|
9312
10043
|
var fileReaderSchema = z.object({
|
|
9313
10044
|
path: z.string().describe("Path to the file to read"),
|
|
9314
10045
|
encoding: z.enum(["utf8", "utf-8", "ascii", "base64", "hex", "binary"]).default("utf8").describe("File encoding")
|
|
@@ -10640,7 +11371,7 @@ var AskHumanInputSchema = z.object({
|
|
|
10640
11371
|
suggestions: z.array(z.string()).optional().describe("Suggested responses for the human")
|
|
10641
11372
|
});
|
|
10642
11373
|
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
|
|
10643
|
-
var
|
|
11374
|
+
var logger19 = createLogger("agentforge:tools:agent:ask-human", { level: logLevel3 });
|
|
10644
11375
|
function isRecord3(value) {
|
|
10645
11376
|
return typeof value === "object" && value !== null;
|
|
10646
11377
|
}
|
|
@@ -10705,7 +11436,7 @@ function createAskHumanTool() {
|
|
|
10705
11436
|
suggestions: validatedInput.suggestions,
|
|
10706
11437
|
status: "pending"
|
|
10707
11438
|
};
|
|
10708
|
-
|
|
11439
|
+
logger19.debug("About to call interrupt()", {
|
|
10709
11440
|
humanRequest: {
|
|
10710
11441
|
id: humanRequest.id,
|
|
10711
11442
|
question: humanRequest.question,
|
|
@@ -10721,13 +11452,13 @@ function createAskHumanTool() {
|
|
|
10721
11452
|
let response;
|
|
10722
11453
|
try {
|
|
10723
11454
|
response = interrupt(humanRequest);
|
|
10724
|
-
|
|
11455
|
+
logger19.debug("interrupt() returned successfully", {
|
|
10725
11456
|
responseType: typeof response,
|
|
10726
11457
|
hasResponse: response != null,
|
|
10727
11458
|
...typeof response === "string" ? { responseLength: response.length } : {}
|
|
10728
11459
|
});
|
|
10729
11460
|
} catch (error) {
|
|
10730
|
-
|
|
11461
|
+
logger19.debug("interrupt() threw error (expected for GraphInterrupt)", {
|
|
10731
11462
|
...error && typeof error === "object" && "constructor" in error ? { errorType: error.constructor?.name } : {},
|
|
10732
11463
|
error: error instanceof Error ? error.message : String(error)
|
|
10733
11464
|
});
|
|
@@ -10752,6 +11483,6 @@ function createAskHumanTool() {
|
|
|
10752
11483
|
}
|
|
10753
11484
|
var askHumanTool = createAskHumanTool();
|
|
10754
11485
|
|
|
10755
|
-
export { AskHumanInputSchema, CalculatorSchema, ConnectionManager, ConnectionState, CreditCardValidatorSchema, CurrentDateTimeSchema, DEFAULT_BATCH_SIZE, DEFAULT_CHUNK_SIZE, DEFAULT_DESTINATION_POLICY, DEFAULT_FILE_SYSTEM_POLICY, DEFAULT_RETRY_CONFIG2 as DEFAULT_RETRY_CONFIG, DateArithmeticSchema, DateComparisonSchema, DateDifferenceSchema, DateFormatterSchema, DestinationPolicyError, DuckDuckGoProvider, EmailValidatorSchema, EmbeddingManager, FileSystemPolicy, FileSystemPolicyError, HttpMethod, IpValidatorSchema, MAX_BATCH_SIZE, MathFunctionsSchema, MissingPeerDependencyError, OpenAIEmbeddingProvider, PhoneValidatorSchema, RandomNumberSchema, SchemaInspector, SerperProvider, StatisticsSchema, StringCaseConverterSchema, StringJoinSchema, StringLengthSchema, StringReplaceSchema, StringSplitSchema, StringSubstringSchema, StringTrimSchema, UrlValidatorSimpleSchema, UuidValidatorSchema, archiveConfluencePage, arrayFilter, arrayFilterSchema, arrayGroupBy, arrayGroupBySchema, arrayMap, arrayMapSchema, arraySort, arraySortSchema, askHumanTool, assertDestinationAllowed, benchmarkBatchExecution, benchmarkStreamingSelectMemory, buildDeleteQuery, buildInsertQuery, buildSelectQuery, buildUpdateQuery, calculator, checkPeerDependency, confluenceTools, createArrayFilterTool, createArrayGroupByTool, createArrayMapTool, createArraySortTool, createAskHumanTool, createCalculatorTool, createConfluencePage, createConfluenceTools, createCreditCardValidatorTool, createCsvGeneratorTool, createCsvParserTool, createCsvToJsonTool, createCsvTools, createCurrentDateTimeTool, createDateArithmeticTool, createDateComparisonTool, createDateDifferenceTool, createDateFormatterTool, createDateTimeTools, createDirectoryCreateTool, createDirectoryDeleteTool, createDirectoryListTool, createDirectoryOperationTools, createDuckDuckGoProvider, createEmailValidatorTool, createExtractImagesTool, createExtractLinksTool, createFileAppendTool, createFileDeleteTool, createFileExistsTool, createFileOperationTools, createFileReaderTool, createFileSearchTool, createFileSystemPolicy, createFileWriterTool, createHtmlParserTool, createHtmlParserTools, createHttpTools, createIpValidatorTool, createJsonMergeTool, createJsonParserTool, createJsonQueryTool, createJsonStringifyTool, createJsonToCsvTool, createJsonToXmlTool, createJsonTools, createJsonValidatorTool, createMathFunctionsTool, createMathOperationTools, createModelSafeToolPreset, createNeo4jCreateNodeWithEmbeddingTool, createNeo4jFindNodesTool, createNeo4jGetSchemaTool, createNeo4jQueryTool, createNeo4jTools, createNeo4jTraverseTool, createNeo4jVectorSearchTool, createNeo4jVectorSearchWithEmbeddingTool, createObjectOmitTool, createObjectPickTool, createPathBasenameTool, createPathDirnameTool, createPathExtensionTool, createPathJoinTool, createPathNormalizeTool, createPathParseTool, createPathRelativeTool, createPathResolveTool, createPathUtilityTools, createPhoneValidatorTool, createRandomNumberTool, createScraperTools, createSelectReadableStream, createSerperProvider, createSlackTools, createStatisticsTool, createStringCaseConverterTool, createStringJoinTool, createStringLengthTool, createStringReplaceTool, createStringSplitTool, createStringSubstringTool, createStringTrimTool, createStringUtilityTools, createTransformerTools, createUrlBuilderTool, createUrlQueryParserTool, createUrlValidatorSimpleTool, createUrlValidatorTool, createUrlValidatorTools, createUuidValidatorTool, createValidationTools, createWebScraperTool, createXmlGeneratorTool, createXmlParserTool, createXmlToJsonTool, createXmlTools, creditCardValidator, csvGenerator, csvGeneratorSchema, csvParser, csvParserSchema, csvToJson, csvToJsonSchema, csvTools, currentDateTime, dateArithmetic, dateComparison, dateDifference, dateFormatter, dateTimeTools, diffSchemas, directoryCreate, directoryCreateSchema, directoryDelete, directoryDeleteSchema, directoryList, directoryListSchema, directoryOperationTools, emailValidator, embeddingManager, executeBatchedTask, executeQuery2 as executeQuery, executeStreamingSelect, exportSchemaToJson, extractImages, extractImagesSchema, extractLinks, extractLinksSchema, fileAppend, fileAppendSchema, fileDelete, fileDeleteSchema, fileExists, fileExistsSchema, fileOperationTools, fileReader, fileReaderSchema, fileSearch, fileSearchSchema, fileWriter, fileWriterSchema, generateBatchEmbeddings, generateEmbedding, getCohereApiKey, getConfluencePage, getEmbeddingModel, getEmbeddingProvider, getHuggingFaceApiKey, getInstallationInstructions, getOllamaBaseUrl, getOpenAIApiKey, getPeerDependencyName, getSlackChannels, getSlackMessages, getSpacePages, getVendorTypeMap, getVoyageApiKey, htmlParser, htmlParserSchema, htmlParserTools, httpClient, httpGet, httpGetSchema, httpPost, httpPostSchema, httpRequestSchema, httpTools, importSchemaFromJson, initializeEmbeddings, initializeEmbeddingsWithConfig, initializeFromEnv, initializeNeo4jTools, ipValidator, isRetryableError2 as isRetryableError, jsonMerge, jsonMergeSchema, jsonParser, jsonParserSchema, jsonQuery, jsonQuerySchema, jsonStringify, jsonStringifySchema, jsonToCsv, jsonToCsvSchema, jsonToXml, jsonToXmlSchema, jsonTools, jsonValidator, jsonValidatorSchema, listConfluenceSpaces, mapColumnType, mapSchemaTypes, mathFunctions, mathOperationTools, neo4jCoreTools, neo4jCreateNodeWithEmbedding, neo4jCreateNodeWithEmbeddingSchema, neo4jFindNodes, neo4jFindNodesSchema, neo4jGetSchema, neo4jGetSchemaSchema, neo4jPool, neo4jQuery, neo4jQuerySchema, neo4jTools, neo4jTraverse, neo4jTraverseSchema, neo4jVectorSearch, neo4jVectorSearchSchema, neo4jVectorSearchWithEmbedding, neo4jVectorSearchWithEmbeddingSchema, notifySlack, objectOmit, objectOmitSchema, objectPick, objectPickSchema, pathBasename, pathBasenameSchema, pathDirname, pathDirnameSchema, pathExtension, pathExtensionSchema, pathJoin, pathJoinSchema, pathNormalize, pathNormalizeSchema, pathParse, pathParseSchema, pathRelative, pathRelativeSchema, pathResolve, pathResolveSchema, pathUtilityTools, phoneValidator, randomNumber, relationalDelete, relationalGetSchema, relationalInsert, relationalQuery, relationalSelect, relationalUpdate, requestWithDestinationPolicy, resolveFileSystemPolicy, retryWithBackoff2 as retryWithBackoff, scraperTools, searchConfluence, searchResultSchema, sendSlackMessage, slackTools, statistics, streamSelectChunks, stringCaseConverter, stringJoin, stringLength, stringReplace, stringSplit, stringSubstring, stringTrim, stringUtilityTools, transformerArraySchema, transformerObjectSchema, transformerTools, transformerValueSchema, updateConfluencePage, urlBuilder, urlBuilderSchema, urlQueryParser, urlQueryParserSchema, urlValidator, urlValidatorSchema, urlValidatorSimple, urlValidatorTools, uuidValidator, validateBatch, validateColumnTypes, validateColumnsExist, validateTableExists, validateText, validationTools, webScraper, webScraperSchema, webSearch, webSearchOutputSchema, webSearchSchema, withProviderErrorMetadata, withTransaction, xmlGenerator, xmlGeneratorSchema, xmlParser, xmlParserSchema, xmlToJson, xmlToJsonSchema, xmlTools };
|
|
11486
|
+
export { AskHumanInputSchema, CalculatorSchema, ConnectionManager, ConnectionState, CreditCardValidatorSchema, CurrentDateTimeSchema, DEFAULT_BATCH_SIZE, DEFAULT_CHUNK_SIZE, DEFAULT_DESTINATION_POLICY, DEFAULT_FILE_SYSTEM_POLICY, DEFAULT_RETRY_CONFIG2 as DEFAULT_RETRY_CONFIG, DateArithmeticSchema, DateComparisonSchema, DateDifferenceSchema, DateFormatterSchema, DestinationPolicyError, DuckDuckGoProvider, EmailValidatorSchema, EmbeddingManager, FileSystemPolicy, FileSystemPolicyError, HttpMethod, IpValidatorSchema, MAX_BATCH_SIZE, MathFunctionsSchema, MissingPeerDependencyError, OpenAIEmbeddingProvider, PhoneValidatorSchema, RandomNumberSchema, RelationalToolSetConfigurationError, RelationalToolSetDisposedError, RelationalTransactionError, SchemaCache, SchemaInspector, SerperProvider, StatisticsSchema, StringCaseConverterSchema, StringJoinSchema, StringLengthSchema, StringReplaceSchema, StringSplitSchema, StringSubstringSchema, StringTrimSchema, UrlValidatorSimpleSchema, UuidValidatorSchema, archiveConfluencePage, arrayFilter, arrayFilterSchema, arrayGroupBy, arrayGroupBySchema, arrayMap, arrayMapSchema, arraySort, arraySortSchema, askHumanTool, assertDestinationAllowed, benchmarkBatchExecution, benchmarkStreamingSelectMemory, buildDeleteQuery, buildInsertQuery, buildSelectQuery, buildUpdateQuery, calculator, checkPeerDependency, confluenceTools, createArrayFilterTool, createArrayGroupByTool, createArrayMapTool, createArraySortTool, createAskHumanTool, createCalculatorTool, createConfluencePage, createConfluenceTools, createCreditCardValidatorTool, createCsvGeneratorTool, createCsvParserTool, createCsvToJsonTool, createCsvTools, createCurrentDateTimeTool, createDateArithmeticTool, createDateComparisonTool, createDateDifferenceTool, createDateFormatterTool, createDateTimeTools, createDirectoryCreateTool, createDirectoryDeleteTool, createDirectoryListTool, createDirectoryOperationTools, createDuckDuckGoProvider, createEmailValidatorTool, createExtractImagesTool, createExtractLinksTool, createFileAppendTool, createFileDeleteTool, createFileExistsTool, createFileOperationTools, createFileReaderTool, createFileSearchTool, createFileSystemPolicy, createFileWriterTool, createHtmlParserTool, createHtmlParserTools, createHttpTools, createIpValidatorTool, createJsonMergeTool, createJsonParserTool, createJsonQueryTool, createJsonStringifyTool, createJsonToCsvTool, createJsonToXmlTool, createJsonTools, createJsonValidatorTool, createMathFunctionsTool, createMathOperationTools, createModelSafeToolPreset, createNeo4jCreateNodeWithEmbeddingTool, createNeo4jFindNodesTool, createNeo4jGetSchemaTool, createNeo4jQueryTool, createNeo4jTools, createNeo4jTraverseTool, createNeo4jVectorSearchTool, createNeo4jVectorSearchWithEmbeddingTool, createObjectOmitTool, createObjectPickTool, createPathBasenameTool, createPathDirnameTool, createPathExtensionTool, createPathJoinTool, createPathNormalizeTool, createPathParseTool, createPathRelativeTool, createPathResolveTool, createPathUtilityTools, createPhoneValidatorTool, createRandomNumberTool, createRelationalToolSet, createScraperTools, createSelectReadableStream, createSerperProvider, createSlackTools, createStatisticsTool, createStringCaseConverterTool, createStringJoinTool, createStringLengthTool, createStringReplaceTool, createStringSplitTool, createStringSubstringTool, createStringTrimTool, createStringUtilityTools, createTransformerTools, createUrlBuilderTool, createUrlQueryParserTool, createUrlValidatorSimpleTool, createUrlValidatorTool, createUrlValidatorTools, createUuidValidatorTool, createValidationTools, createWebScraperTool, createXmlGeneratorTool, createXmlParserTool, createXmlToJsonTool, createXmlTools, creditCardValidator, csvGenerator, csvGeneratorSchema, csvParser, csvParserSchema, csvToJson, csvToJsonSchema, csvTools, currentDateTime, dateArithmetic, dateComparison, dateDifference, dateFormatter, dateTimeTools, diffSchemas, directoryCreate, directoryCreateSchema, directoryDelete, directoryDeleteSchema, directoryList, directoryListSchema, directoryOperationTools, emailValidator, embeddingManager, executeBatchedTask, executeQuery2 as executeQuery, executeStreamingSelect, exportSchemaToJson, extractImages, extractImagesSchema, extractLinks, extractLinksSchema, fileAppend, fileAppendSchema, fileDelete, fileDeleteSchema, fileExists, fileExistsSchema, fileOperationTools, fileReader, fileReaderSchema, fileSearch, fileSearchSchema, fileWriter, fileWriterSchema, generateBatchEmbeddings, generateEmbedding, getCohereApiKey, getConfluencePage, getEmbeddingModel, getEmbeddingProvider, getHuggingFaceApiKey, getInstallationInstructions, getOllamaBaseUrl, getOpenAIApiKey, getPeerDependencyName, getSlackChannels, getSlackMessages, getSpacePages, getVendorTypeMap, getVoyageApiKey, htmlParser, htmlParserSchema, htmlParserTools, httpClient, httpGet, httpGetSchema, httpPost, httpPostSchema, httpRequestSchema, httpTools, importSchemaFromJson, initializeEmbeddings, initializeEmbeddingsWithConfig, initializeFromEnv, initializeNeo4jTools, ipValidator, isRetryableError2 as isRetryableError, jsonMerge, jsonMergeSchema, jsonParser, jsonParserSchema, jsonQuery, jsonQuerySchema, jsonStringify, jsonStringifySchema, jsonToCsv, jsonToCsvSchema, jsonToXml, jsonToXmlSchema, jsonTools, jsonValidator, jsonValidatorSchema, listConfluenceSpaces, mapColumnType, mapSchemaTypes, mathFunctions, mathOperationTools, neo4jCoreTools, neo4jCreateNodeWithEmbedding, neo4jCreateNodeWithEmbeddingSchema, neo4jFindNodes, neo4jFindNodesSchema, neo4jGetSchema, neo4jGetSchemaSchema, neo4jPool, neo4jQuery, neo4jQuerySchema, neo4jTools, neo4jTraverse, neo4jTraverseSchema, neo4jVectorSearch, neo4jVectorSearchSchema, neo4jVectorSearchWithEmbedding, neo4jVectorSearchWithEmbeddingSchema, notifySlack, objectOmit, objectOmitSchema, objectPick, objectPickSchema, pathBasename, pathBasenameSchema, pathDirname, pathDirnameSchema, pathExtension, pathExtensionSchema, pathJoin, pathJoinSchema, pathNormalize, pathNormalizeSchema, pathParse, pathParseSchema, pathRelative, pathRelativeSchema, pathResolve, pathResolveSchema, pathUtilityTools, phoneValidator, randomNumber, relationalDelete, relationalGetSchema, relationalInsert, relationalQuery, relationalSelect, relationalUpdate, requestWithDestinationPolicy, resolveFileSystemPolicy, retryWithBackoff2 as retryWithBackoff, scraperTools, searchConfluence, searchResultSchema, sendSlackMessage, slackTools, statistics, streamSelectChunks, stringCaseConverter, stringJoin, stringLength, stringReplace, stringSplit, stringSubstring, stringTrim, stringUtilityTools, transformerArraySchema, transformerObjectSchema, transformerTools, transformerValueSchema, updateConfluencePage, urlBuilder, urlBuilderSchema, urlQueryParser, urlQueryParserSchema, urlValidator, urlValidatorSchema, urlValidatorSimple, urlValidatorTools, uuidValidator, validateBatch, validateColumnTypes, validateColumnsExist, validateTableExists, validateText, validationTools, webScraper, webScraperSchema, webSearch, webSearchOutputSchema, webSearchSchema, withProviderErrorMetadata, withTransaction, xmlGenerator, xmlGeneratorSchema, xmlParser, xmlParserSchema, xmlToJson, xmlToJsonSchema, xmlTools };
|
|
10756
11487
|
//# sourceMappingURL=index.js.map
|
|
10757
11488
|
//# sourceMappingURL=index.js.map
|