@blackbox_ai/blackbox-cli-core 0.0.7 → 0.8.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.
Files changed (255) hide show
  1. package/README.md +11 -183
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/blackbox/blackboxOAuth2.js +17 -1
  6. package/dist/src/blackbox/blackboxOAuth2.js.map +1 -1
  7. package/dist/src/code_assist/oauth2.js +15 -3
  8. package/dist/src/code_assist/oauth2.js.map +1 -1
  9. package/dist/src/config/blackboxModels.d.ts +3 -2
  10. package/dist/src/config/blackboxModels.js +262 -33
  11. package/dist/src/config/blackboxModels.js.map +1 -1
  12. package/dist/src/config/config.d.ts +65 -0
  13. package/dist/src/config/config.js +282 -17
  14. package/dist/src/config/config.js.map +1 -1
  15. package/dist/src/config/models.d.ts +1 -1
  16. package/dist/src/config/models.js +1 -1
  17. package/dist/src/config/models.js.map +1 -1
  18. package/dist/src/config/multiAgentModels.d.ts +63 -0
  19. package/dist/src/config/multiAgentModels.js +194 -0
  20. package/dist/src/config/multiAgentModels.js.map +1 -0
  21. package/dist/src/core/client.js +11 -5
  22. package/dist/src/core/client.js.map +1 -1
  23. package/dist/src/core/contentGenerator.d.ts +1 -0
  24. package/dist/src/core/contentGenerator.js +57 -7
  25. package/dist/src/core/contentGenerator.js.map +1 -1
  26. package/dist/src/core/coreToolScheduler.js +2 -2
  27. package/dist/src/core/coreToolScheduler.js.map +1 -1
  28. package/dist/src/core/encryptedClientFactory.d.ts +17 -0
  29. package/dist/src/core/encryptedClientFactory.js +92 -0
  30. package/dist/src/core/encryptedClientFactory.js.map +1 -0
  31. package/dist/src/core/encryptedContentGenerator.d.ts +47 -0
  32. package/dist/src/core/encryptedContentGenerator.js +445 -0
  33. package/dist/src/core/encryptedContentGenerator.js.map +1 -0
  34. package/dist/src/core/encryptedGeminiClient.d.ts +59 -0
  35. package/dist/src/core/encryptedGeminiClient.js +177 -0
  36. package/dist/src/core/encryptedGeminiClient.js.map +1 -0
  37. package/dist/src/core/encryptedGeminiClientBridge.d.ts +107 -0
  38. package/dist/src/core/encryptedGeminiClientBridge.js +808 -0
  39. package/dist/src/core/encryptedGeminiClientBridge.js.map +1 -0
  40. package/dist/src/core/encryptedGeminiClientWrapper.d.ts +129 -0
  41. package/dist/src/core/encryptedGeminiClientWrapper.js +305 -0
  42. package/dist/src/core/encryptedGeminiClientWrapper.js.map +1 -0
  43. package/dist/src/core/encryptedTurn.d.ts +40 -0
  44. package/dist/src/core/encryptedTurn.js +114 -0
  45. package/dist/src/core/encryptedTurn.js.map +1 -0
  46. package/dist/src/core/logger.d.ts +21 -0
  47. package/dist/src/core/logger.js +110 -0
  48. package/dist/src/core/logger.js.map +1 -1
  49. package/dist/src/core/openaiContentGenerator/constants.d.ts +2 -0
  50. package/dist/src/core/openaiContentGenerator/constants.js +2 -0
  51. package/dist/src/core/openaiContentGenerator/constants.js.map +1 -1
  52. package/dist/src/core/openaiContentGenerator/converter.d.ts +16 -1
  53. package/dist/src/core/openaiContentGenerator/converter.js +135 -4
  54. package/dist/src/core/openaiContentGenerator/converter.js.map +1 -1
  55. package/dist/src/core/openaiContentGenerator/pipeline.js +22 -8
  56. package/dist/src/core/openaiContentGenerator/pipeline.js.map +1 -1
  57. package/dist/src/core/openaiContentGenerator/pipeline.test.js +51 -0
  58. package/dist/src/core/openaiContentGenerator/pipeline.test.js.map +1 -1
  59. package/dist/src/core/openaiContentGenerator/provider/default.js +10 -1
  60. package/dist/src/core/openaiContentGenerator/provider/default.js.map +1 -1
  61. package/dist/src/core/prompts.d.ts +18 -1
  62. package/dist/src/core/prompts.js +388 -459
  63. package/dist/src/core/prompts.js.map +1 -1
  64. package/dist/src/core/tokenLimits.d.ts +1 -0
  65. package/dist/src/core/tokenLimits.js +37 -2
  66. package/dist/src/core/tokenLimits.js.map +1 -1
  67. package/dist/src/core/tokenLimits.test.js +36 -1
  68. package/dist/src/core/tokenLimits.test.js.map +1 -1
  69. package/dist/src/encrypt/attestation.d.ts +5 -0
  70. package/dist/src/encrypt/attestation.js +100 -0
  71. package/dist/src/encrypt/attestation.js.map +1 -0
  72. package/dist/src/encrypt/client.d.ts +14 -0
  73. package/dist/src/encrypt/client.js +132 -0
  74. package/dist/src/encrypt/client.js.map +1 -0
  75. package/dist/src/encrypt/config.d.ts +22 -0
  76. package/dist/src/encrypt/config.js +43 -0
  77. package/dist/src/encrypt/config.js.map +1 -0
  78. package/dist/src/encrypt/crypto-utils.d.ts +57 -0
  79. package/dist/src/encrypt/crypto-utils.js +257 -0
  80. package/dist/src/encrypt/crypto-utils.js.map +1 -0
  81. package/dist/src/encrypt/history-manager.d.ts +43 -0
  82. package/dist/src/encrypt/history-manager.js +164 -0
  83. package/dist/src/encrypt/history-manager.js.map +1 -0
  84. package/dist/src/encrypt/minimax-template.d.ts +73 -0
  85. package/dist/src/encrypt/minimax-template.js +276 -0
  86. package/dist/src/encrypt/minimax-template.js.map +1 -0
  87. package/dist/src/encrypt/sessions.d.ts +17 -0
  88. package/dist/src/encrypt/sessions.js +221 -0
  89. package/dist/src/encrypt/sessions.js.map +1 -0
  90. package/dist/src/encrypt/streaming-client.d.ts +29 -0
  91. package/dist/src/encrypt/streaming-client.js +232 -0
  92. package/dist/src/encrypt/streaming-client.js.map +1 -0
  93. package/dist/src/encrypt/tool-formatter.d.ts +36 -0
  94. package/dist/src/encrypt/tool-formatter.js +353 -0
  95. package/dist/src/encrypt/tool-formatter.js.map +1 -0
  96. package/dist/src/encrypt/tool-parser.d.ts +93 -0
  97. package/dist/src/encrypt/tool-parser.js +567 -0
  98. package/dist/src/encrypt/tool-parser.js.map +1 -0
  99. package/dist/src/encrypt/types.d.ts +81 -0
  100. package/dist/src/encrypt/types.js +2 -0
  101. package/dist/src/encrypt/types.js.map +1 -0
  102. package/dist/src/generated/git-commit.d.ts +3 -3
  103. package/dist/src/generated/git-commit.js +3 -3
  104. package/dist/src/ide/ide-client.js +9 -19
  105. package/dist/src/ide/ide-client.js.map +1 -1
  106. package/dist/src/index.d.ts +15 -0
  107. package/dist/src/index.js +15 -0
  108. package/dist/src/index.js.map +1 -1
  109. package/dist/src/mcp/oauth-provider.js +2 -6
  110. package/dist/src/mcp/oauth-provider.js.map +1 -1
  111. package/dist/src/mcp/oauth-token-storage.d.ts +7 -0
  112. package/dist/src/mcp/oauth-token-storage.js +24 -0
  113. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  114. package/dist/src/services/EncryptedChatService.d.ts +80 -0
  115. package/dist/src/services/EncryptedChatService.js +202 -0
  116. package/dist/src/services/EncryptedChatService.js.map +1 -0
  117. package/dist/src/services/StatsHistoryService.d.ts +131 -0
  118. package/dist/src/services/StatsHistoryService.js +427 -0
  119. package/dist/src/services/StatsHistoryService.js.map +1 -0
  120. package/dist/src/services/checkpointApiService.d.ts +101 -0
  121. package/dist/src/services/checkpointApiService.js +215 -0
  122. package/dist/src/services/checkpointApiService.js.map +1 -0
  123. package/dist/src/services/environmentSanitization.d.ts +24 -0
  124. package/dist/src/services/environmentSanitization.js +152 -0
  125. package/dist/src/services/environmentSanitization.js.map +1 -0
  126. package/dist/src/telemetry/blackbox-logger/blackbox-logger.d.ts +2 -6
  127. package/dist/src/telemetry/blackbox-logger/blackbox-logger.js +29 -135
  128. package/dist/src/telemetry/blackbox-logger/blackbox-logger.js.map +1 -1
  129. package/dist/src/telemetry/blackbox-logger/blackbox-logger.test.js +1 -1
  130. package/dist/src/telemetry/blackbox-logger/blackbox-logger.test.js.map +1 -1
  131. package/dist/src/telemetry/uiTelemetry.d.ts +8 -0
  132. package/dist/src/telemetry/uiTelemetry.js +17 -0
  133. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  134. package/dist/src/tools/browser-interactive.d.ts +63 -0
  135. package/dist/src/tools/browser-interactive.js +394 -0
  136. package/dist/src/tools/browser-interactive.js.map +1 -0
  137. package/dist/src/tools/browser_use.d.ts +23 -2
  138. package/dist/src/tools/browser_use.js +424 -43
  139. package/dist/src/tools/browser_use.js.map +1 -1
  140. package/dist/src/tools/data-file-constants.d.ts +17 -0
  141. package/dist/src/tools/data-file-constants.js +30 -0
  142. package/dist/src/tools/data-file-constants.js.map +1 -0
  143. package/dist/src/tools/edit.js +44 -7
  144. package/dist/src/tools/edit.js.map +1 -1
  145. package/dist/src/tools/exitPlanMode.js +1 -1
  146. package/dist/src/tools/exitPlanMode.js.map +1 -1
  147. package/dist/src/tools/ls.js +40 -6
  148. package/dist/src/tools/ls.js.map +1 -1
  149. package/dist/src/tools/ls.test.js +4 -4
  150. package/dist/src/tools/ls.test.js.map +1 -1
  151. package/dist/src/tools/mcp-client-manager.d.ts +28 -2
  152. package/dist/src/tools/mcp-client-manager.js +62 -4
  153. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  154. package/dist/src/tools/mcp-client.d.ts +5 -3
  155. package/dist/src/tools/mcp-client.js +39 -11
  156. package/dist/src/tools/mcp-client.js.map +1 -1
  157. package/dist/src/tools/mcp-tool.d.ts +3 -1
  158. package/dist/src/tools/mcp-tool.js +37 -9
  159. package/dist/src/tools/mcp-tool.js.map +1 -1
  160. package/dist/src/tools/memoryTool.d.ts +14 -4
  161. package/dist/src/tools/memoryTool.js +98 -39
  162. package/dist/src/tools/memoryTool.js.map +1 -1
  163. package/dist/src/tools/read-data-file.d.ts +31 -0
  164. package/dist/src/tools/read-data-file.js +469 -0
  165. package/dist/src/tools/read-data-file.js.map +1 -0
  166. package/dist/src/tools/read-file.js +64 -5
  167. package/dist/src/tools/read-file.js.map +1 -1
  168. package/dist/src/tools/read-file.test.js +40 -6
  169. package/dist/src/tools/read-file.test.js.map +1 -1
  170. package/dist/src/tools/shell.d.ts +3 -1
  171. package/dist/src/tools/shell.js +25 -4
  172. package/dist/src/tools/shell.js.map +1 -1
  173. package/dist/src/tools/skill.d.ts +34 -0
  174. package/dist/src/tools/skill.js +143 -0
  175. package/dist/src/tools/skill.js.map +1 -0
  176. package/dist/src/tools/sql_db.d.ts +101 -0
  177. package/dist/src/tools/sql_db.js +1033 -0
  178. package/dist/src/tools/sql_db.js.map +1 -0
  179. package/dist/src/tools/sql_db_configure.d.ts +18 -0
  180. package/dist/src/tools/sql_db_configure.js +96 -0
  181. package/dist/src/tools/sql_db_configure.js.map +1 -0
  182. package/dist/src/tools/taskCompletion.d.ts +29 -0
  183. package/dist/src/tools/taskCompletion.js +231 -0
  184. package/dist/src/tools/taskCompletion.js.map +1 -0
  185. package/dist/src/tools/todoWrite.js +0 -142
  186. package/dist/src/tools/todoWrite.js.map +1 -1
  187. package/dist/src/tools/tool-error.d.ts +3 -1
  188. package/dist/src/tools/tool-error.js +3 -0
  189. package/dist/src/tools/tool-error.js.map +1 -1
  190. package/dist/src/tools/tool-names.d.ts +8 -0
  191. package/dist/src/tools/tool-names.js +8 -0
  192. package/dist/src/tools/tool-names.js.map +1 -1
  193. package/dist/src/tools/tool-registry.d.ts +22 -0
  194. package/dist/src/tools/tool-registry.js +41 -1
  195. package/dist/src/tools/tool-registry.js.map +1 -1
  196. package/dist/src/tools/tools.d.ts +18 -2
  197. package/dist/src/tools/tools.js +3 -0
  198. package/dist/src/tools/tools.js.map +1 -1
  199. package/dist/src/tools/web-fetch.js +24 -4
  200. package/dist/src/tools/web-fetch.js.map +1 -1
  201. package/dist/src/tools/web-search.js +160 -2
  202. package/dist/src/tools/web-search.js.map +1 -1
  203. package/dist/src/tools/workspace-error-helper.d.ts +9 -0
  204. package/dist/src/tools/workspace-error-helper.js +43 -0
  205. package/dist/src/tools/workspace-error-helper.js.map +1 -0
  206. package/dist/src/tools/workspace-error-helper.test.js +85 -0
  207. package/dist/src/tools/workspace-error-helper.test.js.map +1 -0
  208. package/dist/src/tools/write-file.js +42 -7
  209. package/dist/src/tools/write-file.js.map +1 -1
  210. package/dist/src/utils/environmentContext.js +3 -1
  211. package/dist/src/utils/environmentContext.js.map +1 -1
  212. package/dist/src/utils/environmentContext.test.js +3 -2
  213. package/dist/src/utils/environmentContext.test.js.map +1 -1
  214. package/dist/src/utils/fetch.d.ts +3 -1
  215. package/dist/src/utils/fetch.js +35 -2
  216. package/dist/src/utils/fetch.js.map +1 -1
  217. package/dist/src/utils/fileUtils.js +30 -3
  218. package/dist/src/utils/fileUtils.js.map +1 -1
  219. package/dist/src/utils/filesearch/fileSearch.d.ts +2 -0
  220. package/dist/src/utils/filesearch/fileSearch.js +38 -7
  221. package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
  222. package/dist/src/utils/git-worktree-utils.d.ts +56 -0
  223. package/dist/src/utils/git-worktree-utils.js +176 -0
  224. package/dist/src/utils/git-worktree-utils.js.map +1 -0
  225. package/dist/src/utils/imageCompression.d.ts +34 -0
  226. package/dist/src/utils/imageCompression.js +170 -0
  227. package/dist/src/utils/imageCompression.js.map +1 -0
  228. package/dist/src/utils/messageTruncator.d.ts +51 -0
  229. package/dist/src/utils/messageTruncator.js +346 -0
  230. package/dist/src/utils/messageTruncator.js.map +1 -0
  231. package/dist/src/utils/pathReader.js +26 -6
  232. package/dist/src/utils/pathReader.js.map +1 -1
  233. package/dist/src/utils/skill.d.ts +65 -0
  234. package/dist/src/utils/skill.js +241 -0
  235. package/dist/src/utils/skill.js.map +1 -0
  236. package/dist/src/utils/textCleaning.d.ts +51 -0
  237. package/dist/src/utils/textCleaning.js +327 -0
  238. package/dist/src/utils/textCleaning.js.map +1 -0
  239. package/dist/tsconfig.tsbuildinfo +1 -1
  240. package/package.json +19 -6
  241. package/dist/src/tools/mcp-client-manager.test.js +0 -39
  242. package/dist/src/tools/mcp-client-manager.test.js.map +0 -1
  243. package/dist/src/tools/mcp-client.test.d.ts +0 -6
  244. package/dist/src/tools/mcp-client.test.js +0 -454
  245. package/dist/src/tools/mcp-client.test.js.map +0 -1
  246. package/dist/src/tools/mcp-tool.test.d.ts +0 -6
  247. package/dist/src/tools/mcp-tool.test.js +0 -576
  248. package/dist/src/tools/mcp-tool.test.js.map +0 -1
  249. package/dist/src/tools/memoryTool.test.d.ts +0 -6
  250. package/dist/src/tools/memoryTool.test.js +0 -420
  251. package/dist/src/tools/memoryTool.test.js.map +0 -1
  252. package/dist/src/tools/tool-registry.test.d.ts +0 -6
  253. package/dist/src/tools/tool-registry.test.js +0 -332
  254. package/dist/src/tools/tool-registry.test.js.map +0 -1
  255. /package/dist/src/tools/{mcp-client-manager.test.d.ts → workspace-error-helper.test.d.ts} +0 -0
@@ -0,0 +1,1033 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import pg from 'pg';
7
+ import { MongoClient } from 'mongodb';
8
+ import { createClient } from 'redis';
9
+ import { ToolNames } from './tool-names.js';
10
+ import { ToolErrorType } from './tool-error.js';
11
+ import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from './tools.js';
12
+ async function loadMysqlPromiseModule() {
13
+ try {
14
+ // eslint-disable-next-line import/no-internal-modules
15
+ const mod = await import('mysql2/promise');
16
+ return mod.default ?? mod;
17
+ }
18
+ catch (_error) {
19
+ const message = _error instanceof Error ? _error.message : String(_error);
20
+ throw new Error(`MySQL driver failed to load (${message}). ` +
21
+ `If you don't need MySQL, ignore this. Otherwise reinstall dependencies (mysql2 requires lru.min and seq-queue).`);
22
+ }
23
+ }
24
+ const CAUTION_PROMPT = `Carefully understand the database and the fields. Make sure you have an accurate understanding of the fields and the values.
25
+ If required, run further queries with 'sql_db_query' to have a full understanding. Run as many query as needed to confirm the results and provide accurate information to the user.
26
+
27
+ Note: To avoid timeout. Unless explicitly asked, Do not run global count operation.`;
28
+ /**
29
+ * Singleton database session manager (supports PostgreSQL, MySQL, MongoDB, and Redis)
30
+ */
31
+ export class SqlSessionManager {
32
+ pgPool;
33
+ mysqlPool;
34
+ mongoClient;
35
+ mongoDb;
36
+ redisClient;
37
+ connectionString;
38
+ dbType;
39
+ static instance;
40
+ constructor() { }
41
+ static getInstance() {
42
+ if (!SqlSessionManager.instance) {
43
+ SqlSessionManager.instance = new SqlSessionManager();
44
+ }
45
+ return SqlSessionManager.instance;
46
+ }
47
+ async connect(connectionString, dbType, includeSchema = true, mongoDbName) {
48
+ if (dbType === 'mongodb' && !mongoDbName) {
49
+ throw new Error('MongoDB database name is required. Get the database name from the user if its not mentioned.Then, try again.');
50
+ }
51
+ console.log(`Connecting to ${dbType.toUpperCase()} database...`);
52
+ if (this.connectionString === connectionString && this.dbType === dbType) {
53
+ console.log('Reusing existing connection pool');
54
+ if (!includeSchema) {
55
+ return {
56
+ success: true,
57
+ message: 'Already connected to database',
58
+ };
59
+ }
60
+ // Fetch fresh schema
61
+ try {
62
+ const schema = await this.fetchSchema(mongoDbName);
63
+ return {
64
+ success: true,
65
+ message: 'Already connected to database',
66
+ schema,
67
+ };
68
+ }
69
+ catch (error) {
70
+ return {
71
+ success: false,
72
+ message: 'Connected but failed to fetch schema',
73
+ error: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+ }
77
+ // Close existing connection if different
78
+ if (this.pgPool || this.mysqlPool || this.mongoClient || this.redisClient) {
79
+ await this.disconnect();
80
+ }
81
+ try {
82
+ this.dbType = dbType;
83
+ this.connectionString = connectionString;
84
+ if (dbType === 'postgres') {
85
+ this.pgPool = new pg.Pool({
86
+ connectionString,
87
+ max: 10,
88
+ idleTimeoutMillis: 30000,
89
+ connectionTimeoutMillis: 10000,
90
+ });
91
+ // Test connection
92
+ const client = await this.pgPool.connect();
93
+ try {
94
+ await client.query('SELECT NOW()');
95
+ console.log('PostgreSQL connection successful');
96
+ }
97
+ finally {
98
+ client.release();
99
+ }
100
+ }
101
+ else if (dbType === 'mysql') {
102
+ // Parse connection string for MySQL
103
+ const url = new URL(connectionString);
104
+ const mysql = (await loadMysqlPromiseModule());
105
+ this.mysqlPool = mysql.createPool({
106
+ host: url.hostname,
107
+ port: url.port ? parseInt(url.port, 10) : 3306,
108
+ user: url.username,
109
+ password: url.password,
110
+ database: url.pathname.slice(1), // Remove leading /
111
+ waitForConnections: true,
112
+ connectionLimit: 10,
113
+ queueLimit: 0,
114
+ connectTimeout: 10000,
115
+ });
116
+ // Test connection
117
+ const connection = await this.mysqlPool.getConnection();
118
+ try {
119
+ await connection.query('SELECT NOW()');
120
+ console.log('MySQL connection successful');
121
+ }
122
+ finally {
123
+ connection.release();
124
+ }
125
+ }
126
+ else if (dbType === 'mongodb') {
127
+ // Parse connection string for MongoDB
128
+ this.mongoClient = new MongoClient(connectionString);
129
+ await this.mongoClient.connect();
130
+ // Get database name from connection string or use default
131
+ const url = new URL(connectionString);
132
+ const dbName = mongoDbName || url.pathname.slice(1);
133
+ this.mongoDb = this.mongoClient.db(dbName);
134
+ // Test connection
135
+ await this.mongoDb.command({ ping: 1 });
136
+ console.log('MongoDB connection successful');
137
+ }
138
+ else if (dbType === 'redis') {
139
+ const normalizeRedisUrl = (url, useTls) => {
140
+ if (useTls) {
141
+ return url.startsWith('redis://')
142
+ ? url.replace(/^redis:\/\//, 'rediss://')
143
+ : url;
144
+ }
145
+ return url.startsWith('rediss://')
146
+ ? url.replace(/^rediss:\/\//, 'redis://')
147
+ : url;
148
+ };
149
+ const connectRedis = async (url) => {
150
+ const client = createClient({ url });
151
+ client.on('error', (error) => {
152
+ console.warn(`Redis client error: ${error instanceof Error ? error.message : String(error)}`);
153
+ });
154
+ await client.connect();
155
+ return client;
156
+ };
157
+ const primaryUrl = normalizeRedisUrl(connectionString, true);
158
+ const fallbackUrl = normalizeRedisUrl(connectionString, false);
159
+ try {
160
+ // Try TLS first
161
+ this.redisClient = await connectRedis(primaryUrl);
162
+ }
163
+ catch (_error) {
164
+ if (this.redisClient) {
165
+ await this.redisClient.quit().catch(() => { });
166
+ this.redisClient = undefined;
167
+ }
168
+ // Retry without TLS
169
+ this.redisClient = await connectRedis(fallbackUrl);
170
+ }
171
+ if (!this.redisClient) {
172
+ throw new Error('Failed to initialize Redis client');
173
+ }
174
+ // Test connection
175
+ await this.redisClient.ping();
176
+ console.log('Redis connection successful');
177
+ }
178
+ else {
179
+ throw new Error(`Unsupported database type: ${dbType}`);
180
+ }
181
+ if (!includeSchema) {
182
+ return {
183
+ success: true,
184
+ message: `Successfully connected to ${dbType.toUpperCase()} database`,
185
+ };
186
+ }
187
+ // Fetch schema information
188
+ const schema = await this.fetchSchema(mongoDbName);
189
+ return {
190
+ success: true,
191
+ message: `Successfully connected to ${dbType.toUpperCase()} database`,
192
+ schema,
193
+ };
194
+ }
195
+ catch (error) {
196
+ const errorMessage = `Failed to connect to database: ${error instanceof Error ? error.message : String(error)}`;
197
+ console.error(`[Error] ${errorMessage}`);
198
+ this.pgPool = undefined;
199
+ this.mysqlPool = undefined;
200
+ this.connectionString = undefined;
201
+ this.dbType = undefined;
202
+ return {
203
+ success: false,
204
+ message: errorMessage,
205
+ error: errorMessage,
206
+ };
207
+ }
208
+ }
209
+ async fetchSchema(mongoDbName) {
210
+ if (!this.dbType) {
211
+ throw new Error('Not connected to database');
212
+ }
213
+ if (this.dbType === 'postgres') {
214
+ return this.fetchPostgresSchema();
215
+ }
216
+ else if (this.dbType === 'mysql') {
217
+ return this.fetchMySQLSchema();
218
+ }
219
+ else if (this.dbType === 'mongodb') {
220
+ return this.fetchMongoDBSchema(mongoDbName);
221
+ }
222
+ else if (this.dbType === 'redis') {
223
+ return this.fetchRedisSchema();
224
+ }
225
+ throw new Error(`Unsupported database type for schema fetch: ${this.dbType}`);
226
+ }
227
+ async fetchPostgresSchema() {
228
+ if (!this.pgPool) {
229
+ throw new Error('Not connected to PostgreSQL database');
230
+ }
231
+ const client = await this.pgPool.connect();
232
+ try {
233
+ // Get list of tables
234
+ const tablesResult = await client.query(`SELECT table_name, table_schema
235
+ FROM information_schema.tables
236
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
237
+ ORDER BY table_schema, table_name`);
238
+ const tables = tablesResult.rows;
239
+ // Get columns for each table
240
+ const columns = {};
241
+ for (const table of tables) {
242
+ const key = `${table.table_schema}.${table.table_name}`;
243
+ // Get columns
244
+ const columnsResult = await client.query(`SELECT column_name, data_type, is_nullable
245
+ FROM information_schema.columns
246
+ WHERE table_schema = $1 AND table_name = $2
247
+ ORDER BY ordinal_position`, [table.table_schema, table.table_name]);
248
+ columns[key] = columnsResult.rows;
249
+ }
250
+ return {
251
+ tables,
252
+ columns,
253
+ };
254
+ }
255
+ finally {
256
+ client.release();
257
+ }
258
+ }
259
+ async fetchMySQLSchema() {
260
+ if (!this.mysqlPool) {
261
+ throw new Error('Not connected to MySQL database');
262
+ }
263
+ const connection = await this.mysqlPool.getConnection();
264
+ try {
265
+ // Get current database
266
+ const [dbResult] = await connection.query('SELECT DATABASE() as db');
267
+ const dbRows = dbResult;
268
+ const currentDb = dbRows[0]?.db || '';
269
+ // Get list of tables
270
+ const [tablesResult] = await connection.query(`SELECT table_name, table_schema
271
+ FROM information_schema.tables
272
+ WHERE table_schema = ?
273
+ ORDER BY table_name`, [currentDb]);
274
+ const tablesRows = tablesResult;
275
+ const tables = tablesRows.map((row) => ({
276
+ table_name: row.table_name || row.TABLE_NAME || '',
277
+ table_schema: row.table_schema || row.TABLE_SCHEMA || '',
278
+ }));
279
+ // Get columns for each table
280
+ const columns = {};
281
+ for (const table of tables) {
282
+ const key = `${table.table_schema}.${table.table_name}`;
283
+ // Get columns
284
+ const [columnsResult] = await connection.query(`SELECT column_name, data_type, is_nullable
285
+ FROM information_schema.columns
286
+ WHERE table_schema = ? AND table_name = ?
287
+ ORDER BY ordinal_position`, [table.table_schema, table.table_name]);
288
+ const columnsRows = columnsResult;
289
+ columns[key] = columnsRows.map((col) => ({
290
+ column_name: col.column_name || col.COLUMN_NAME || '',
291
+ data_type: col.data_type || col.DATA_TYPE || '',
292
+ is_nullable: col.is_nullable || col.IS_NULLABLE || '',
293
+ }));
294
+ }
295
+ return {
296
+ tables,
297
+ columns,
298
+ };
299
+ }
300
+ finally {
301
+ connection.release();
302
+ }
303
+ }
304
+ async fetchMongoDBSchema(mongoDbName) {
305
+ if (!this.mongoDb) {
306
+ throw new Error('Not connected to MongoDB database');
307
+ }
308
+ try {
309
+ if (!this.mongoClient) {
310
+ throw new Error('MongoDB client is not initialized');
311
+ }
312
+ let dbNames = [];
313
+ if (mongoDbName) {
314
+ dbNames = [mongoDbName];
315
+ }
316
+ else {
317
+ dbNames = [this.mongoDb.databaseName];
318
+ }
319
+ // Sample documents from each collection to infer schema
320
+ const collections = [];
321
+ const sampleDocuments = {};
322
+ for (const dbName of dbNames) {
323
+ const db = this.mongoClient.db(dbName);
324
+ const collectionsResult = await db
325
+ .listCollections({}, { nameOnly: true, authorizedCollections: true })
326
+ .toArray();
327
+ for (const col of collectionsResult) {
328
+ collections.push({
329
+ collection_name: col.name,
330
+ database_name: dbName,
331
+ });
332
+ }
333
+ for (const col of collectionsResult) {
334
+ const key = `${dbName}.${col.name}`;
335
+ // Sample a document to infer fields
336
+ const sampleDoc = await db.collection(col.name).findOne();
337
+ sampleDocuments[key] = [sampleDoc];
338
+ }
339
+ }
340
+ return {
341
+ tables: [],
342
+ collections,
343
+ sampleDocuments,
344
+ };
345
+ }
346
+ catch (error) {
347
+ console.error('Error fetching MongoDB schema:', error);
348
+ throw error;
349
+ }
350
+ }
351
+ async fetchRedisSchema() {
352
+ if (!this.redisClient) {
353
+ throw new Error('Not connected to Redis database');
354
+ }
355
+ try {
356
+ // Redis doesn't have a traditional schema
357
+ // We'll scan for a minimal sample of keys and get their types
358
+ const keyInfo = [];
359
+ const limitPerType = 3;
360
+ const maxScans = 20;
361
+ const maxSamples = limitPerType * 6;
362
+ const typeCounts = {};
363
+ let cursor = 0;
364
+ let scans = 0;
365
+ while (scans < maxScans && keyInfo.length < maxSamples) {
366
+ const scanResult = await this.redisClient.scan(cursor, {
367
+ MATCH: '*',
368
+ COUNT: 200,
369
+ });
370
+ cursor = scanResult.cursor;
371
+ scans += 1;
372
+ for (const key of scanResult.keys) {
373
+ const type = await this.redisClient.type(key);
374
+ const count = typeCounts[type] || 0;
375
+ if (count >= limitPerType) {
376
+ continue;
377
+ }
378
+ keyInfo.push({ key, type });
379
+ typeCounts[type] = count + 1;
380
+ if (keyInfo.length >= maxSamples) {
381
+ break;
382
+ }
383
+ }
384
+ if (cursor === 0) {
385
+ break;
386
+ }
387
+ }
388
+ // Group by type
389
+ const typeGroups = {};
390
+ for (const info of keyInfo) {
391
+ typeGroups[info.type] = (typeGroups[info.type] || 0) + 1;
392
+ }
393
+ const redisKeys = [];
394
+ for (const type of Object.keys(typeGroups)) {
395
+ const keys = keyInfo.filter((k) => k.type === type).slice(0, limitPerType).map((k) => k.key);
396
+ for (const key of keys) {
397
+ try {
398
+ let values = [];
399
+ if (type === 'string') {
400
+ const value = await this.redisClient.get(key);
401
+ values = value === null ? [] : [value];
402
+ }
403
+ else if (type === 'hash') {
404
+ const value = await this.redisClient.hGetAll(key);
405
+ const entries = Object.entries(value).slice(0, 3);
406
+ values = [Object.fromEntries(entries)];
407
+ }
408
+ else if (type === 'list') {
409
+ values = await this.redisClient.lRange(key, 0, 2);
410
+ }
411
+ else if (type === 'set') {
412
+ values = (await this.redisClient.sMembers(key)).slice(0, 3);
413
+ }
414
+ else if (type === 'zset') {
415
+ values = await this.redisClient.zRange(key, 0, 2);
416
+ }
417
+ else if (type === 'stream') {
418
+ values = await this.redisClient.xRange(key, '-', '+', { COUNT: 3 });
419
+ }
420
+ redisKeys.push({ key, type, values });
421
+ }
422
+ catch (_) {
423
+ redisKeys.push({ key, type, values: [] });
424
+ }
425
+ }
426
+ }
427
+ return {
428
+ tables: [],
429
+ redisKeys,
430
+ };
431
+ }
432
+ catch (error) {
433
+ console.error('Error fetching Redis schema:', error);
434
+ throw error;
435
+ }
436
+ }
437
+ async query(sql) {
438
+ if (!this.dbType) {
439
+ return {
440
+ success: false,
441
+ message: 'Not connected to database. Please connect first using sql_db_connect.',
442
+ error: 'No active database connection',
443
+ };
444
+ }
445
+ if (this.dbType === 'postgres') {
446
+ return this.queryPostgres(sql);
447
+ }
448
+ else if (this.dbType === 'mysql') {
449
+ return this.queryMySQL(sql);
450
+ }
451
+ else if (this.dbType === 'mongodb') {
452
+ return this.queryMongoDB(sql);
453
+ }
454
+ else if (this.dbType === 'redis') {
455
+ return this.queryRedis(sql);
456
+ }
457
+ return {
458
+ success: false,
459
+ message: `Unsupported database type for queries: ${this.dbType}`,
460
+ error: `Unsupported database type: ${this.dbType}`,
461
+ };
462
+ }
463
+ async queryPostgres(sql) {
464
+ if (!this.pgPool) {
465
+ return {
466
+ success: false,
467
+ message: 'Not connected to PostgreSQL database.',
468
+ error: 'No active database connection',
469
+ };
470
+ }
471
+ const client = await this.pgPool.connect();
472
+ try {
473
+ console.log('Executing readonly query on PostgreSQL...');
474
+ // Start a readonly transaction
475
+ await client.query('BEGIN TRANSACTION READ ONLY');
476
+ try {
477
+ const result = await client.query(sql);
478
+ await client.query('COMMIT');
479
+ console.log(`Query executed successfully, returned ${result.rowCount} rows`);
480
+ return {
481
+ success: true,
482
+ message: `Query executed successfully, returned ${result.rowCount || 0} rows`,
483
+ rows: result.rows,
484
+ rowCount: result.rowCount || 0,
485
+ };
486
+ }
487
+ catch (queryError) {
488
+ await client.query('ROLLBACK').catch(() => { });
489
+ throw queryError;
490
+ }
491
+ }
492
+ catch (error) {
493
+ const errorMessage = `Query execution failed: ${error instanceof Error ? error.message : String(error)}`;
494
+ console.error(`[Error] ${errorMessage}`);
495
+ return {
496
+ success: false,
497
+ message: errorMessage,
498
+ error: errorMessage,
499
+ };
500
+ }
501
+ finally {
502
+ client.release();
503
+ }
504
+ }
505
+ async queryMySQL(sql) {
506
+ if (!this.mysqlPool) {
507
+ return {
508
+ success: false,
509
+ message: 'Not connected to MySQL database.',
510
+ error: 'No active database connection',
511
+ };
512
+ }
513
+ const connection = await this.mysqlPool.getConnection();
514
+ try {
515
+ console.log('Executing readonly query on MySQL...');
516
+ // Start a readonly transaction
517
+ await connection.query('START TRANSACTION READ ONLY');
518
+ try {
519
+ const [rows] = await connection.query(sql);
520
+ await connection.query('COMMIT');
521
+ const rowsArray = Array.isArray(rows) ? rows : [];
522
+ console.log(`Query executed successfully, returned ${rowsArray.length} rows`);
523
+ return {
524
+ success: true,
525
+ message: `Query executed successfully, returned ${rowsArray.length} rows`,
526
+ rows: rowsArray,
527
+ rowCount: rowsArray.length,
528
+ };
529
+ }
530
+ catch (queryError) {
531
+ await connection.query('ROLLBACK').catch(() => { });
532
+ throw queryError;
533
+ }
534
+ }
535
+ catch (error) {
536
+ const errorMessage = `Query execution failed: ${error instanceof Error ? error.message : String(error)}`;
537
+ console.error(`[Error] ${errorMessage}`);
538
+ return {
539
+ success: false,
540
+ message: errorMessage,
541
+ error: errorMessage,
542
+ };
543
+ }
544
+ finally {
545
+ connection.release();
546
+ }
547
+ }
548
+ async queryMongoDB(queryString) {
549
+ if (!this.mongoDb) {
550
+ return {
551
+ success: false,
552
+ message: 'Not connected to MongoDB database.',
553
+ error: 'No active database connection',
554
+ };
555
+ }
556
+ try {
557
+ console.log('Executing query on MongoDB...');
558
+ // Parse the query string as JSON
559
+ // Expected format: { "collection": "collectionName", "operation": "find", "query": {...}, "options": {...} }
560
+ const queryObj = JSON.parse(queryString);
561
+ if (!queryObj.collection) {
562
+ throw new Error('Missing "collection" field in query');
563
+ }
564
+ const collection = this.mongoDb.collection(queryObj.collection);
565
+ const operation = queryObj.operation || 'find';
566
+ const query = queryObj.query || {};
567
+ const options = queryObj.options || {};
568
+ let rows = [];
569
+ if (operation === 'find') {
570
+ const limit = options.limit || 100; // Default limit
571
+ rows = await collection.find(query).limit(limit).toArray();
572
+ }
573
+ else if (operation === 'aggregate') {
574
+ const pipeline = Array.isArray(queryObj.query)
575
+ ? queryObj.query
576
+ : Array.isArray(queryObj.pipeline)
577
+ ? queryObj.pipeline
578
+ : [];
579
+ rows = await collection.aggregate(pipeline, options).toArray();
580
+ }
581
+ else if (operation === 'count' || operation === 'countDocuments') {
582
+ const count = await collection.countDocuments(query);
583
+ rows = [{ count }];
584
+ }
585
+ else if (operation === 'distinct') {
586
+ const field = queryObj.field;
587
+ if (!field) {
588
+ throw new Error('Missing "field" for distinct operation');
589
+ }
590
+ const distinctValues = await collection.distinct(field, query);
591
+ rows = distinctValues.map((value) => ({ value }));
592
+ }
593
+ else {
594
+ throw new Error(`Unsupported MongoDB operation: ${operation}`);
595
+ }
596
+ console.log(`Query executed successfully, returned ${rows.length} documents`);
597
+ return {
598
+ success: true,
599
+ message: `Query executed successfully, returned ${rows.length} documents`,
600
+ rows,
601
+ rowCount: rows.length,
602
+ };
603
+ }
604
+ catch (error) {
605
+ const errorMessage = `Query execution failed: ${error instanceof Error ? error.message : String(error)}`;
606
+ console.error(`[Error] ${errorMessage}`);
607
+ return {
608
+ success: false,
609
+ message: errorMessage,
610
+ error: errorMessage,
611
+ };
612
+ }
613
+ }
614
+ async queryRedis(commandString) {
615
+ if (!this.redisClient) {
616
+ return {
617
+ success: false,
618
+ message: 'Not connected to Redis database.',
619
+ error: 'No active database connection',
620
+ };
621
+ }
622
+ try {
623
+ console.log('Executing command on Redis...');
624
+ // Parse the command string as JSON
625
+ // Expected format: { "command": "GET", "args": ["key"] }
626
+ const commandObj = JSON.parse(commandString);
627
+ if (!commandObj.command) {
628
+ throw new Error('Missing "command" field in query');
629
+ }
630
+ const command = commandObj.command.toLowerCase();
631
+ const args = commandObj.args || [];
632
+ let result;
633
+ let rows = [];
634
+ // Execute common Redis commands
635
+ if (command === 'get') {
636
+ result = await this.redisClient.get(args[0]);
637
+ rows = [{ key: args[0], value: result }];
638
+ }
639
+ else if (command === 'mget') {
640
+ result = await this.redisClient.mGet(args);
641
+ rows = args.map((key, index) => ({ key, value: result[index] }));
642
+ }
643
+ else if (command === 'keys') {
644
+ const keys = await this.redisClient.keys(args[0] || '*');
645
+ rows = keys.slice(0, 100).map((key) => ({ key })); // Limit to 100
646
+ }
647
+ else if (command === 'scan') {
648
+ const cursor = parseInt(args[0] || '0', 10);
649
+ const pattern = args[1] || '*';
650
+ const count = parseInt(args[2] || '10', 10);
651
+ const scanResult = await this.redisClient.scan(cursor, { MATCH: pattern, COUNT: count });
652
+ rows = [{ cursor: scanResult.cursor, keys: scanResult.keys }];
653
+ }
654
+ else if (command === 'hgetall') {
655
+ result = await this.redisClient.hGetAll(args[0]);
656
+ rows = Object.entries(result).map(([field, value]) => ({ field, value }));
657
+ }
658
+ else if (command === 'lrange') {
659
+ const start = parseInt(args[1] || '0', 10);
660
+ const stop = parseInt(args[2] || '-1', 10);
661
+ result = await this.redisClient.lRange(args[0], start, stop);
662
+ rows = result.map((value, index) => ({ index: start + index, value }));
663
+ }
664
+ else if (command === 'smembers') {
665
+ result = await this.redisClient.sMembers(args[0]);
666
+ rows = result.map((value) => ({ value }));
667
+ }
668
+ else if (command === 'zrange') {
669
+ const start = parseInt(args[1] || '0', 10);
670
+ const stop = parseInt(args[2] || '-1', 10);
671
+ result = await this.redisClient.zRange(args[0], start, stop);
672
+ rows = result.map((value, index) => ({ index: start + index, value }));
673
+ }
674
+ else if (command === 'ttl') {
675
+ result = await this.redisClient.ttl(args[0]);
676
+ rows = [{ key: args[0], ttl: result }];
677
+ }
678
+ else if (command === 'type') {
679
+ result = await this.redisClient.type(args[0]);
680
+ rows = [{ key: args[0], type: result }];
681
+ }
682
+ else {
683
+ throw new Error(`Unsupported Redis command: ${command}. Use common read commands like GET, KEYS, HGETALL, LRANGE, etc.`);
684
+ }
685
+ console.log(`Command executed successfully, returned ${rows.length} results`);
686
+ return {
687
+ success: true,
688
+ message: `Command executed successfully, returned ${rows.length} results`,
689
+ rows,
690
+ rowCount: rows.length,
691
+ };
692
+ }
693
+ catch (error) {
694
+ const errorMessage = `Command execution failed: ${error instanceof Error ? error.message : String(error)}`;
695
+ console.error(`[Error] ${errorMessage}`);
696
+ return {
697
+ success: false,
698
+ message: errorMessage,
699
+ error: errorMessage,
700
+ };
701
+ }
702
+ }
703
+ async disconnect() {
704
+ if (this.pgPool) {
705
+ console.log('Closing PostgreSQL connection pool...');
706
+ try {
707
+ await this.pgPool.end();
708
+ console.log('PostgreSQL connection pool closed successfully');
709
+ }
710
+ catch (error) {
711
+ console.warn(`Error closing PostgreSQL connection pool: ${error instanceof Error ? error.message : String(error)}`);
712
+ }
713
+ }
714
+ if (this.mysqlPool) {
715
+ console.log('Closing MySQL connection pool...');
716
+ try {
717
+ await this.mysqlPool.end();
718
+ console.log('MySQL connection pool closed successfully');
719
+ }
720
+ catch (error) {
721
+ console.warn(`Error closing MySQL connection pool: ${error instanceof Error ? error.message : String(error)}`);
722
+ }
723
+ }
724
+ if (this.mongoClient) {
725
+ console.log('Closing MongoDB connection...');
726
+ try {
727
+ await this.mongoClient.close();
728
+ console.log('MongoDB connection closed successfully');
729
+ }
730
+ catch (error) {
731
+ console.warn(`Error closing MongoDB connection: ${error instanceof Error ? error.message : String(error)}`);
732
+ }
733
+ }
734
+ if (this.redisClient) {
735
+ console.log('Closing Redis connection...');
736
+ try {
737
+ await this.redisClient.quit();
738
+ console.log('Redis connection closed successfully');
739
+ }
740
+ catch (error) {
741
+ console.warn(`Error closing Redis connection: ${error instanceof Error ? error.message : String(error)}`);
742
+ }
743
+ }
744
+ this.pgPool = undefined;
745
+ this.mysqlPool = undefined;
746
+ this.mongoClient = undefined;
747
+ this.mongoDb = undefined;
748
+ this.redisClient = undefined;
749
+ this.connectionString = undefined;
750
+ this.dbType = undefined;
751
+ }
752
+ isConnected() {
753
+ return !!(this.pgPool || this.mysqlPool || this.mongoClient || this.redisClient);
754
+ }
755
+ getDatabaseType() {
756
+ return this.dbType;
757
+ }
758
+ }
759
+ class SqlDbConnectToolInvocation extends BaseToolInvocation {
760
+ constructor(params) {
761
+ super(params);
762
+ }
763
+ getDescription() {
764
+ return `Connecting to ${this.params.type.toUpperCase()} database`;
765
+ }
766
+ async execute() {
767
+ const session = SqlSessionManager.getInstance();
768
+ const connectionString = this.params.connectionString || process.env['DB_CONNECTION_URI'];
769
+ if (!connectionString) {
770
+ return {
771
+ llmContent: 'DB_CONNECTION_URI is not configured. Run `/db configure` to set it, then retry this request.',
772
+ returnDisplay: 'DB_CONNECTION_URI is not configured. Run /db configure and try again.',
773
+ };
774
+ }
775
+ if (this.params.type === 'postgres') {
776
+ try {
777
+ const url = new URL(connectionString);
778
+ if (!url.pathname || url.pathname === '/' || url.pathname === '') {
779
+ return {
780
+ llmContent: 'PostgreSQL connection URI is missing a database name (path). Ask the user the name of DB and try again. ',
781
+ returnDisplay: 'PostgreSQL connection URI missing database name. Ask the user the name of DB and try again',
782
+ };
783
+ }
784
+ }
785
+ catch {
786
+ // If parsing fails, let the underlying connection attempt surface details.
787
+ }
788
+ }
789
+ const result = await session.connect(connectionString, this.params.type, this.params.includeSchema ?? true, this.params.mongoDbName ?? undefined);
790
+ const maxSchemaTokens = 50000 * 3;
791
+ console.log('[SqlDbConnectTool] Connection completed', {
792
+ success: result.success,
793
+ dbType: this.params.type,
794
+ tableCount: result.schema?.tables.length || 0,
795
+ });
796
+ if (!result.success) {
797
+ return {
798
+ llmContent: result.message,
799
+ returnDisplay: result.message,
800
+ error: {
801
+ message: result.error || result.message,
802
+ type: ToolErrorType.SQL_CONNECTION_ERROR,
803
+ },
804
+ };
805
+ }
806
+ // Format schema information for LLM
807
+ let schemaInfo = `${result.message}\n\n`;
808
+ if (result.schema) {
809
+ schemaInfo += `Found ${result.schema.tables.length} tables:\n\n`;
810
+ for (const table of result.schema.tables) {
811
+ const key = `${table.table_schema}.${table.table_name}`;
812
+ const columns = result.schema.columns?.[key] || [];
813
+ schemaInfo += `**${key}**\n`;
814
+ schemaInfo += `Columns (${columns.length}):\n`;
815
+ for (const col of columns) {
816
+ schemaInfo += ` - ${col.column_name}: ${col.data_type}${col.is_nullable === 'NO' ? ' NOT NULL' : ''}\n`;
817
+ }
818
+ schemaInfo += `\n`;
819
+ }
820
+ if (result.schema.tables.length > 0) {
821
+ schemaInfo += `\n\n ${CAUTION_PROMPT}`;
822
+ }
823
+ if (result.schema.collections) {
824
+ schemaInfo += `Collections (${result.schema.collections.length}):\n\n`;
825
+ for (const col of result.schema.collections) {
826
+ schemaInfo += ` - ${col.collection_name} in ${col.database_name}\n`;
827
+ }
828
+ schemaInfo += '\n';
829
+ }
830
+ if (result.schema.sampleDocuments) {
831
+ schemaInfo += `Sample documents:\n\n`;
832
+ for (const key in result.schema.sampleDocuments) {
833
+ const doc = result.schema.sampleDocuments[key];
834
+ schemaInfo += ` - Collection: ${key}\n`;
835
+ schemaInfo += ` - Sample Document: ${JSON.stringify(doc)}\n`;
836
+ }
837
+ schemaInfo += `\n\n ${CAUTION_PROMPT}`;
838
+ }
839
+ if (result.schema.redisKeys) {
840
+ schemaInfo += `Redis keys:\n\n`;
841
+ for (const key of result.schema.redisKeys) {
842
+ schemaInfo += ` - Key: ${key.key}\n`;
843
+ schemaInfo += ` - Type: ${key.type}\n`;
844
+ schemaInfo += ` - Values: ${JSON.stringify(key.values)}\n`;
845
+ }
846
+ schemaInfo += `\n\n ${CAUTION_PROMPT}`;
847
+ }
848
+ }
849
+ // truncate the schema info to 50000 tokens
850
+ if (schemaInfo.length > maxSchemaTokens) {
851
+ schemaInfo = schemaInfo.slice(0, maxSchemaTokens) + '\n\n... [truncated]';
852
+ schemaInfo += '\n\n[Schema truncated to prevent context overflow. Use sql_db_query to get more data as needed.]';
853
+ }
854
+ return {
855
+ llmContent: schemaInfo,
856
+ returnDisplay: result.message,
857
+ };
858
+ }
859
+ }
860
+ export class SqlDbConnectTool extends BaseDeclarativeTool {
861
+ config;
862
+ static Name = ToolNames.SQL_DB_CONNECT;
863
+ // @ts-expect-error - Required by base class pattern
864
+ constructor(config) {
865
+ super(SqlDbConnectTool.Name, 'SqlDbConnect', 'Connects to a database (PostgreSQL, MySQL, MongoDB, or Redis) and returns the schema details. The connection persists for subsequent queries. Connection string formats: postgresql://user:password@host:port/database, mysql://user:password@host:port/database, mongodb://user:password@host:port/database, redis://user:password@host:port/database', Kind.Execute, {
866
+ type: 'object',
867
+ properties: {
868
+ connectionString: {
869
+ type: 'string',
870
+ description: 'Database connection string. If omitted, DB_CONNECTION_URI environment variable will be used.',
871
+ },
872
+ type: {
873
+ type: 'string',
874
+ enum: ['postgres', 'mysql', 'mongodb', 'redis'],
875
+ description: 'Database type: "postgres", "mysql", "mongodb", or "redis"',
876
+ },
877
+ mongoDbName: {
878
+ type: 'string',
879
+ description: 'MongoDB database name to connect to (required for MongoDB type). If not provided, assume the current database.',
880
+ },
881
+ includeSchema: {
882
+ type: 'boolean',
883
+ description: 'Whether to fetch and return database schema information (default: true)',
884
+ },
885
+ },
886
+ required: ['type'],
887
+ }, true);
888
+ this.config = config;
889
+ }
890
+ validateToolParamValues(params) {
891
+ const connectionString = params.connectionString?.trim() || process.env['DB_CONNECTION_URI'];
892
+ if (!connectionString) {
893
+ return 'DB_CONNECTION_URI is not set. Run /db configure or provide connectionString.';
894
+ }
895
+ if (!params.type) {
896
+ return 'Database type parameter is required (postgres, mysql, mongodb, or redis)';
897
+ }
898
+ const validTypes = ['postgres', 'mysql', 'mongodb', 'redis'];
899
+ if (!validTypes.includes(params.type)) {
900
+ return `Database type must be one of: ${validTypes.join(', ')}`;
901
+ }
902
+ // Basic validation based on type
903
+ if (params.type === 'postgres') {
904
+ if (!connectionString.startsWith('postgres://') &&
905
+ !connectionString.startsWith('postgresql://')) {
906
+ return 'PostgreSQL connection string must start with postgresql:// or postgres://';
907
+ }
908
+ }
909
+ else if (params.type === 'mysql') {
910
+ if (!connectionString.startsWith('mysql://')) {
911
+ return 'MySQL connection string must start with mysql://';
912
+ }
913
+ }
914
+ else if (params.type === 'mongodb') {
915
+ if (!connectionString.startsWith('mongodb://') &&
916
+ !connectionString.startsWith('mongodb+srv://')) {
917
+ return 'MongoDB connection string must start with mongodb:// or mongodb+srv://';
918
+ }
919
+ }
920
+ else if (params.type === 'redis') {
921
+ if (!connectionString.startsWith('redis://') &&
922
+ !connectionString.startsWith('rediss://')) {
923
+ return 'Redis connection string must start with redis:// or rediss://';
924
+ }
925
+ }
926
+ return null;
927
+ }
928
+ createInvocation(params) {
929
+ return new SqlDbConnectToolInvocation(params);
930
+ }
931
+ }
932
+ class SqlDbQueryToolInvocation extends BaseToolInvocation {
933
+ constructor(params) {
934
+ super(params);
935
+ }
936
+ getDescription() {
937
+ const preview = this.params.sql.length > 100
938
+ ? this.params.sql.substring(0, 100) + '...'
939
+ : this.params.sql;
940
+ return `Executing query: ${preview}`;
941
+ }
942
+ async execute() {
943
+ const session = SqlSessionManager.getInstance();
944
+ if (!session.isConnected()) {
945
+ return {
946
+ llmContent: 'Not connected to database. Please use sql_db_connect first.',
947
+ returnDisplay: 'Not connected to database',
948
+ error: {
949
+ message: 'No active database connection',
950
+ type: ToolErrorType.SQL_CONNECTION_ERROR,
951
+ },
952
+ };
953
+ }
954
+ const maxResultTokens = 50000 * 3;
955
+ const result = await session.query(this.params.sql);
956
+ const dbType = session.getDatabaseType();
957
+ console.log('[SqlDbQueryTool] Query completed', {
958
+ success: result.success,
959
+ dbType,
960
+ rowCount: result.rowCount || 0,
961
+ });
962
+ if (!result.success) {
963
+ return {
964
+ llmContent: result.message,
965
+ returnDisplay: result.message,
966
+ error: {
967
+ message: result.error || result.message,
968
+ type: ToolErrorType.SQL_QUERY_ERROR,
969
+ },
970
+ };
971
+ }
972
+ // Format results for LLM
973
+ let output = `${result.message}\n\n`;
974
+ if (result.rows && result.rows.length > 0) {
975
+ // Show results as formatted JSON
976
+ output += '```json\n';
977
+ output += JSON.stringify(result.rows, null, 2);
978
+ output += '\n```\n';
979
+ // truncate the json to 50000 tokens
980
+ if (output.length > maxResultTokens) {
981
+ output = output.slice(0, maxResultTokens) + '\n\n... [truncated]';
982
+ output += '\n\n[Result truncated to prevent context overflow. To see more results, use a more specific query.]';
983
+ }
984
+ else {
985
+ output += `\n(Showing all ${result.rowCount} rows)\n`;
986
+ }
987
+ }
988
+ else {
989
+ output += 'No rows returned.\n';
990
+ }
991
+ output += `\n\n Note: Now analyze and think through the results to decide the next steps to respond to the user's request accurately. If necessary, use sql_db_query to get more data as needed for better analysis.`;
992
+ return {
993
+ llmContent: output.trim(),
994
+ returnDisplay: result.message,
995
+ };
996
+ }
997
+ }
998
+ export class SqlDbQueryTool extends BaseDeclarativeTool {
999
+ config;
1000
+ static Name = ToolNames.SQL_DB_QUERY;
1001
+ // @ts-expect-error - Required by base class pattern
1002
+ constructor(config) {
1003
+ super(SqlDbQueryTool.Name, 'SqlDbQuery', 'Executes a query on the connected database. For SQL databases (PostgreSQL/MySQL): use SQL SELECT statements. For MongoDB: use JSON format {"collection":"name","operation":"find","query":{...}}. For Redis: use JSON format {"command":"GET","args":["key"]}. SQL queries run in READ ONLY mode.', Kind.Execute, {
1004
+ type: 'object',
1005
+ properties: {
1006
+ sql: {
1007
+ type: 'string',
1008
+ description: 'The query to execute. For SQL: SELECT statements. For MongoDB: JSON with collection, operation (find/aggregate/count/distinct), query, options. For Redis: JSON with command (GET/KEYS/HGETALL/LRANGE/etc) and args array.',
1009
+ },
1010
+ },
1011
+ required: ['sql'],
1012
+ }, true);
1013
+ this.config = config;
1014
+ }
1015
+ validateToolParamValues(params) {
1016
+ if (!params.sql || params.sql.trim() === '') {
1017
+ return 'SQL parameter must be non-empty';
1018
+ }
1019
+ // Warn about potentially dangerous queries (but allow them since we use READ ONLY transaction)
1020
+ const sqlLower = params.sql.trim().toLowerCase();
1021
+ const dangerousKeywords = ['insert', 'update', 'delete', 'drop', 'create', 'alter', 'truncate'];
1022
+ for (const keyword of dangerousKeywords) {
1023
+ if (sqlLower.includes(keyword)) {
1024
+ console.warn(`[Warning] Query contains '${keyword}' but will run in READ ONLY transaction`);
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+ createInvocation(params) {
1030
+ return new SqlDbQueryToolInvocation(params);
1031
+ }
1032
+ }
1033
+ //# sourceMappingURL=sql_db.js.map