From 4162a6491b275392e66eb2a8b7781f752fba0816 Mon Sep 17 00:00:00 2001 From: Syngnat Date: Tue, 9 Jun 2026 03:26:04 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ai-tools):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=A4=96=E9=83=A8=20SQL=20=E7=9B=AE=E5=BD=95=E6=8E=A2?= =?UTF-8?q?=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增外部 SQL 目录快照构建与本地工具执行入口 - 补充内置工具目录、系统提示和状态文案 - 覆盖 AI 面板、工具注册与探针链路测试 --- .../AIChatPanel.message-boundary.test.tsx | 2 + frontend/src/components/AIChatPanel.tsx | 1 + .../ai/AIBuiltinToolsCatalog.test.tsx | 2 + .../components/ai/AIBuiltinToolsCatalog.tsx | 5 + .../ai/aiExternalSqlInsights.test.ts | 79 ++++++++++++ .../components/ai/aiExternalSqlInsights.ts | 117 ++++++++++++++++++ .../components/ai/aiLocalToolExecutor.test.ts | 53 +++++++- .../src/components/ai/aiLocalToolExecutor.ts | 4 + .../ai/aiSnapshotInspectionToolExecutor.ts | 18 +++ .../ai/aiSystemContextMessages.test.ts | 3 +- .../components/ai/aiSystemContextMessages.ts | 6 + .../messageBubble/AIMessageStatusBlocks.tsx | 1 + frontend/src/utils/aiBuiltinToolInfo.ts | 25 ++++ frontend/src/utils/aiToolRegistry.test.ts | 8 ++ 14 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/ai/aiExternalSqlInsights.test.ts create mode 100644 frontend/src/components/ai/aiExternalSqlInsights.ts diff --git a/frontend/src/components/AIChatPanel.message-boundary.test.tsx b/frontend/src/components/AIChatPanel.message-boundary.test.tsx index 19641fb..7345b0d 100644 --- a/frontend/src/components/AIChatPanel.message-boundary.test.tsx +++ b/frontend/src/components/AIChatPanel.message-boundary.test.tsx @@ -45,8 +45,10 @@ describe('AIChatPanel message render isolation', () => { expect(systemContextSource).toContain('inspect_active_tab 读取当前活动页签上下文'); expect(systemContextSource).toContain('inspect_workspace_tabs 盘点当前工作区'); expect(systemContextSource).toContain('inspect_current_connection'); + expect(systemContextSource).toContain('inspect_external_sql_directories'); expect(source).toContain('tabs: useStore.getState().tabs'); expect(source).toContain('activeTabId: useStore.getState().activeTabId'); + expect(source).toContain('externalSQLDirectories: useStore.getState().externalSQLDirectories'); expect(source).toContain('toolContextMap: toolContextMapRef.current'); expect(source).toContain('buildToolResultMessage'); }); diff --git a/frontend/src/components/AIChatPanel.tsx b/frontend/src/components/AIChatPanel.tsx index e1ad4f0..0cbc25e 100644 --- a/frontend/src/components/AIChatPanel.tsx +++ b/frontend/src/components/AIChatPanel.tsx @@ -461,6 +461,7 @@ export const AIChatPanel: React.FC = ({ sqlLogs: useStore.getState().sqlLogs, savedQueries: useStore.getState().savedQueries, sqlSnippets: useStore.getState().sqlSnippets, + externalSQLDirectories: useStore.getState().externalSQLDirectories, skills, userPromptSettings, dynamicModels, diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx index cd062a0..5d54f10 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.test.tsx @@ -46,6 +46,8 @@ describe('AIBuiltinToolsCatalog', () => { expect(markup).toContain('inspect_connection_capabilities'); expect(markup).toContain('盘点本地连接资产'); expect(markup).toContain('inspect_saved_connections'); + expect(markup).toContain('盘点外部 SQL 目录'); + expect(markup).toContain('inspect_external_sql_directories'); expect(markup).toContain('读取当前页签'); expect(markup).toContain('inspect_active_tab'); expect(markup).toContain('盘点当前工作区'); diff --git a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx index 212b57d..4a28fc2 100644 --- a/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx +++ b/frontend/src/components/ai/AIBuiltinToolsCatalog.tsx @@ -87,6 +87,11 @@ const BUILTIN_TOOL_FLOWS = [ steps: 'inspect_saved_connections → inspect_current_connection / get_databases', description: '适合先按关键词或类型筛出本地保存的数据源,再挑目标连接继续看当前状态或库表结构。', }, + { + title: '盘点外部 SQL 目录', + steps: 'inspect_external_sql_directories → inspect_workspace_tabs / inspect_active_tab', + description: '适合先确认本地配置了哪些外部 SQL 目录、目录绑定到哪个连接/库,以及当前打开的 SQL 文件来自哪里,再继续分析脚本内容。', + }, { title: '读取当前页签', steps: 'inspect_active_tab → get_columns / get_indexes / execute_sql', diff --git a/frontend/src/components/ai/aiExternalSqlInsights.test.ts b/frontend/src/components/ai/aiExternalSqlInsights.test.ts new file mode 100644 index 0000000..91d0e23 --- /dev/null +++ b/frontend/src/components/ai/aiExternalSqlInsights.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from 'vitest'; + +import type { ExternalSQLDirectory, SavedConnection, TabData } from '../../types'; +import { buildExternalSQLDirectoriesSnapshot } from './aiExternalSqlInsights'; + +const connections: SavedConnection[] = [ + { + id: 'conn-1', + name: '本地开发库', + config: { + type: 'mysql', + host: '127.0.0.1', + port: 3306, + user: 'root', + }, + }, +]; + +describe('aiExternalSqlInsights', () => { + it('filters configured external sql directories and reports matching open file tabs', () => { + const externalSQLDirectories: ExternalSQLDirectory[] = [ + { + id: 'dir-1', + name: '报表脚本', + path: 'D:/sql/reports', + connectionId: 'conn-1', + dbName: 'crm', + createdAt: 2, + }, + { + id: 'dir-2', + name: '运维脚本', + path: 'D:/sql/ops', + createdAt: 1, + }, + ]; + const tabs: TabData[] = [ + { + id: 'tab-1', + title: '日报.sql', + type: 'query', + connectionId: 'conn-1', + dbName: 'crm', + filePath: 'D:/sql/reports/daily.sql', + query: 'select 1', + }, + { + id: 'tab-2', + title: '用户.sql', + type: 'query', + connectionId: 'conn-1', + dbName: 'crm', + filePath: 'D:/sql/reports/users/detail.sql', + query: 'select 2', + }, + ]; + + const snapshot = buildExternalSQLDirectoriesSnapshot({ + externalSQLDirectories, + connections, + tabs, + keyword: '报表', + }); + + expect(snapshot.totalMatched).toBe(1); + expect(snapshot.returnedDirectories).toBe(1); + expect(snapshot.totalOpenExternalSqlTabs).toBe(2); + expect(snapshot.boundConnectionCount).toBe(1); + expect(snapshot.directories[0]).toMatchObject({ + id: 'dir-1', + connectionName: '本地开发库', + connectionType: 'mysql', + dbName: 'crm', + openFileTabCount: 2, + hasBoundConnection: true, + }); + expect(snapshot.directories[0].openFileTitles[0].title).toBe('日报.sql'); + }); +}); diff --git a/frontend/src/components/ai/aiExternalSqlInsights.ts b/frontend/src/components/ai/aiExternalSqlInsights.ts new file mode 100644 index 0000000..e3f4f90 --- /dev/null +++ b/frontend/src/components/ai/aiExternalSqlInsights.ts @@ -0,0 +1,117 @@ +import type { ExternalSQLDirectory, SavedConnection, TabData } from '../../types'; + +const normalizeLimit = (input: unknown, fallback: number, max: number): number => { + const value = Math.floor(Number(input) || fallback); + if (value < 1) return 1; + if (value > max) return max; + return value; +}; + +const normalizeKeyword = (input: unknown): string => String(input || '').trim().toLowerCase(); + +const normalizePath = (input: unknown): string => + String(input || '').trim().replace(/\\/g, '/').replace(/\/+$/u, ''); + +const matchesKeyword = (keyword: string, fields: Array): boolean => { + if (!keyword) { + return true; + } + return fields.some((field) => String(field || '').toLowerCase().includes(keyword)); +}; + +const belongsToDirectory = (filePath: string, directoryPath: string): boolean => { + if (!filePath || !directoryPath) { + return false; + } + const normalizedFilePath = normalizePath(filePath).toLowerCase(); + const normalizedDirectoryPath = normalizePath(directoryPath).toLowerCase(); + if (!normalizedFilePath || !normalizedDirectoryPath) { + return false; + } + return normalizedFilePath === normalizedDirectoryPath || normalizedFilePath.startsWith(`${normalizedDirectoryPath}/`); +}; + +export const buildExternalSQLDirectoriesSnapshot = (params: { + externalSQLDirectories?: ExternalSQLDirectory[]; + connections: SavedConnection[]; + tabs?: TabData[]; + keyword?: unknown; + connectionId?: unknown; + dbName?: unknown; + limit?: unknown; +}) => { + const { + externalSQLDirectories = [], + connections, + tabs = [], + keyword, + connectionId, + dbName, + limit, + } = params; + + const safeKeyword = normalizeKeyword(keyword); + const safeConnectionId = String(connectionId || '').trim(); + const safeDbName = String(dbName || '').trim(); + const safeLimit = normalizeLimit(limit, 20, 100); + const externalSqlTabs = tabs.filter((tab) => String(tab.filePath || '').trim()); + + const filteredDirectories = [...externalSQLDirectories] + .sort((left, right) => Number(right.createdAt || 0) - Number(left.createdAt || 0)) + .filter((directory) => { + if (safeConnectionId && String(directory.connectionId || '').trim() !== safeConnectionId) { + return false; + } + if (safeDbName && String(directory.dbName || '').trim() !== safeDbName) { + return false; + } + const connection = connections.find((item) => item.id === directory.connectionId); + return matchesKeyword(safeKeyword, [ + directory.id, + directory.name, + directory.path, + directory.connectionId, + directory.dbName, + connection?.name, + connection?.config?.type, + ]); + }); + + const visibleDirectories = filteredDirectories.slice(0, safeLimit).map((directory) => { + const connection = connections.find((item) => item.id === directory.connectionId); + const matchingTabs = externalSqlTabs.filter((tab) => belongsToDirectory(String(tab.filePath || ''), directory.path)); + + return { + id: directory.id, + name: directory.name, + path: directory.path, + connectionId: directory.connectionId || '', + connectionName: connection?.name || '', + connectionType: connection?.config?.type || '', + dbName: directory.dbName || '', + createdAt: Number(directory.createdAt || 0), + hasBoundConnection: Boolean(String(directory.connectionId || '').trim()), + openFileTabCount: matchingTabs.length, + openFileTitles: matchingTabs.slice(0, 5).map((tab) => ({ + tabId: tab.id, + title: tab.title, + filePath: tab.filePath || '', + dbName: tab.dbName || '', + })), + }; + }); + + return { + keyword: safeKeyword, + connectionId: safeConnectionId, + dbName: safeDbName, + limit: safeLimit, + totalMatched: filteredDirectories.length, + returnedDirectories: visibleDirectories.length, + truncated: filteredDirectories.length > visibleDirectories.length, + totalConfiguredDirectories: externalSQLDirectories.length, + totalOpenExternalSqlTabs: externalSqlTabs.length, + boundConnectionCount: filteredDirectories.filter((item) => String(item.connectionId || '').trim()).length, + directories: visibleDirectories, + }; +}; diff --git a/frontend/src/components/ai/aiLocalToolExecutor.test.ts b/frontend/src/components/ai/aiLocalToolExecutor.test.ts index 31ad105..c494e16 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.test.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; -import type { AIMCPToolDescriptor, AIToolCall, SavedConnection } from '../../types'; +import type { AIMCPToolDescriptor, AIToolCall, ExternalSQLDirectory, SavedConnection } from '../../types'; import { buildToolResultMessage, executeLocalAIToolCall } from './aiLocalToolExecutor'; const buildConnection = (): SavedConnection => ({ @@ -605,6 +605,57 @@ describe('aiLocalToolExecutor', () => { expect(result.content).not.toContain('分析仓库'); }); + it('returns configured external sql directories so the model can locate local script assets', async () => { + const externalSQLDirectories: ExternalSQLDirectory[] = [ + { + id: 'dir-1', + name: '报表脚本', + path: 'D:/sql/reports', + connectionId: 'conn-1', + dbName: 'crm', + createdAt: 2, + }, + { + id: 'dir-2', + name: '运维脚本', + path: 'D:/sql/ops', + createdAt: 1, + }, + ]; + const result = await executeLocalAIToolCall({ + toolCall: buildToolCall('inspect_external_sql_directories', { + keyword: '报表', + }), + connections: [buildConnection()], + tabs: [ + { + id: 'tab-1', + title: '日报.sql', + type: 'query', + connectionId: 'conn-1', + dbName: 'crm', + filePath: 'D:/sql/reports/daily.sql', + query: 'select 1', + }, + ], + mcpTools: [], + toolContextMap: new Map(), + externalSQLDirectories, + runtime: { + getDatabases: vi.fn(), + getTables: vi.fn(), + }, + }); + + expect(result.success).toBe(true); + expect(result.content).toContain('"totalMatched":1'); + expect(result.content).toContain('"name":"报表脚本"'); + expect(result.content).toContain('"connectionName":"主库"'); + expect(result.content).toContain('"openFileTabCount":1'); + expect(result.content).toContain('日报.sql'); + expect(result.content).not.toContain('运维脚本'); + }); + it('blocks execute_sql when the AI safety check rejects the statement', async () => { const query = vi.fn(); const result = await executeLocalAIToolCall({ diff --git a/frontend/src/components/ai/aiLocalToolExecutor.ts b/frontend/src/components/ai/aiLocalToolExecutor.ts index 42672db..a3a3dba 100644 --- a/frontend/src/components/ai/aiLocalToolExecutor.ts +++ b/frontend/src/components/ai/aiLocalToolExecutor.ts @@ -9,6 +9,7 @@ import type { SavedConnection, SavedQuery, SqlSnippet, + ExternalSQLDirectory, TabData, } from '../../types'; import { executeDatabaseToolCall } from './aiDatabaseToolExecutor'; @@ -36,6 +37,7 @@ export interface ExecuteLocalAIToolCallOptions { sqlLogs?: SqlLog[]; savedQueries?: SavedQuery[]; sqlSnippets?: SqlSnippet[]; + externalSQLDirectories?: ExternalSQLDirectory[]; skills?: AISkillConfig[]; userPromptSettings?: AIUserPromptSettings; dynamicModels?: string[]; @@ -66,6 +68,7 @@ export async function executeLocalAIToolCall({ sqlLogs = [], savedQueries = [], sqlSnippets = [], + externalSQLDirectories = [], skills = [], userPromptSettings, dynamicModels = [], @@ -92,6 +95,7 @@ export async function executeLocalAIToolCall({ sqlLogs, savedQueries, sqlSnippets, + externalSQLDirectories, skills, userPromptSettings, dynamicModels, diff --git a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts index cdef4dd..d7eca86 100644 --- a/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts +++ b/frontend/src/components/ai/aiSnapshotInspectionToolExecutor.ts @@ -8,6 +8,7 @@ import type { AISafetyLevel, AISkillConfig, AIUserPromptSettings, + ExternalSQLDirectory, SavedConnection, SavedQuery, SqlSnippet, @@ -30,6 +31,7 @@ import { buildSqlSnippetsSnapshot, } from './aiSavedSqlInsights'; import { buildSavedConnectionsSnapshot } from './aiSavedConnectionInsights'; +import { buildExternalSQLDirectoriesSnapshot } from './aiExternalSqlInsights'; import { buildActiveTabSnapshot, buildRecentSqlLogsSnapshot, @@ -64,6 +66,7 @@ interface ExecuteSnapshotInspectionToolCallOptions { sqlLogs?: SqlLog[]; savedQueries?: SavedQuery[]; sqlSnippets?: SqlSnippet[]; + externalSQLDirectories?: ExternalSQLDirectory[]; skills?: AISkillConfig[]; userPromptSettings?: AIUserPromptSettings; dynamicModels?: string[]; @@ -95,6 +98,7 @@ export async function executeSnapshotInspectionToolCall( sqlLogs = [], savedQueries = [], sqlSnippets = [], + externalSQLDirectories = [], skills = [], userPromptSettings, dynamicModels = [], @@ -220,6 +224,19 @@ export async function executeSnapshotInspectionToolCall( })), success: true, }; + case 'inspect_external_sql_directories': + return { + content: JSON.stringify(buildExternalSQLDirectoriesSnapshot({ + externalSQLDirectories, + connections, + tabs, + keyword: args.keyword, + connectionId: args.connectionId, + dbName: args.dbName, + limit: args.limit, + })), + success: true, + }; case 'inspect_active_tab': return { content: JSON.stringify(buildActiveTabSnapshot({ @@ -310,6 +327,7 @@ export async function executeSnapshotInspectionToolCall( inspect_current_connection: '读取当前连接失败', inspect_connection_capabilities: '读取当前连接能力矩阵失败', inspect_saved_connections: '读取本地连接清单失败', + inspect_external_sql_directories: '读取外部 SQL 目录失败', inspect_ai_sessions: '读取本地 AI 会话清单失败', inspect_active_tab: '读取当前活动页签失败', inspect_workspace_tabs: '读取当前工作区页签失败', diff --git a/frontend/src/components/ai/aiSystemContextMessages.test.ts b/frontend/src/components/ai/aiSystemContextMessages.test.ts index 4d07197..238ee01 100644 --- a/frontend/src/components/ai/aiSystemContextMessages.test.ts +++ b/frontend/src/components/ai/aiSystemContextMessages.test.ts @@ -68,7 +68,7 @@ describe('buildAISystemContextMessages', () => { connections: [connections[0]], tabs: [], activeTabId: null, - availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'get_columns'], + availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'get_columns'], skills, userPromptSettings, }); @@ -85,6 +85,7 @@ describe('buildAISystemContextMessages', () => { expect(joined).toContain('inspect_current_connection'); expect(joined).toContain('inspect_connection_capabilities'); expect(joined).toContain('inspect_saved_connections'); + expect(joined).toContain('inspect_external_sql_directories'); expect(joined).toContain('inspect_saved_queries'); expect(joined).toContain('inspect_ai_sessions'); expect(joined).toContain('inspect_sql_snippets'); diff --git a/frontend/src/components/ai/aiSystemContextMessages.ts b/frontend/src/components/ai/aiSystemContextMessages.ts index b58d11d..7bdc2df 100644 --- a/frontend/src/components/ai/aiSystemContextMessages.ts +++ b/frontend/src/components/ai/aiSystemContextMessages.ts @@ -423,6 +423,12 @@ SELECT * FROM users WHERE status = 1; content: '如果用户提到“本地存了哪些连接”“帮我找 mysql / postgres / redis 连接”“哪条连接配了 SSH/代理”,优先调用 inspect_saved_connections 读取真实本地连接清单,再决定继续查看哪条连接。', }); } + if (availableToolNames.includes('inspect_external_sql_directories')) { + systemMessages.push({ + role: 'system', + content: '如果用户提到“外部 SQL 目录”“目录里的脚本”“某个 SQL 文件放在哪个目录”“当前打开的 SQL 文件来自哪里”,优先调用 inspect_external_sql_directories 读取真实外部 SQL 目录资产,再决定继续读取活动页签还是定位具体脚本。', + }); + } if (availableToolNames.includes('inspect_saved_queries')) { systemMessages.push({ role: 'system', diff --git a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx index 275c56f..0b1a5f7 100644 --- a/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx +++ b/frontend/src/components/ai/messageBubble/AIMessageStatusBlocks.tsx @@ -43,6 +43,7 @@ const TOOL_ACTION_LABELS: Record = { inspect_current_connection: '读取当前连接摘要', inspect_connection_capabilities: '读取当前连接能力矩阵', inspect_saved_connections: '盘点本地已保存连接', + inspect_external_sql_directories: '盘点外部 SQL 目录', inspect_ai_sessions: '盘点本地 AI 历史会话', inspect_active_tab: '读取当前活动页签', inspect_workspace_tabs: '盘点当前工作区页签', diff --git a/frontend/src/utils/aiBuiltinToolInfo.ts b/frontend/src/utils/aiBuiltinToolInfo.ts index 5a7542c..3a69a7d 100644 --- a/frontend/src/utils/aiBuiltinToolInfo.ts +++ b/frontend/src/utils/aiBuiltinToolInfo.ts @@ -495,6 +495,31 @@ export const BUILTIN_AI_TOOL_INFO: AIBuiltinToolInfo[] = [ }, }, }, + { + name: "inspect_external_sql_directories", + icon: "🗂️", + desc: "查看本地外部 SQL 目录资产", + detail: + "可按关键词、连接或数据库过滤,返回本地配置的外部 SQL 目录、目录路径、绑定连接/数据库,以及当前是否已经打开这些目录里的 SQL 文件。适合用户提到“外部 SQL 目录”“某个脚本在哪个目录”“现在打开的 SQL 文件来自哪个外部目录”时,先读真实资产。", + params: "keyword?, connectionId?, dbName?, limit?", + tool: { + type: "function", + function: { + name: "inspect_external_sql_directories", + description: + "读取本地配置的外部 SQL 目录清单,可按关键词、连接和数据库过滤,并返回目录路径、绑定连接/数据库,以及当前打开的外部 SQL 文件页签摘要。适用于用户提到外部 SQL 目录、某个 SQL 文件放在哪、当前打开的脚本来自哪个目录时,先读取真实本地资产再回答。", + parameters: { + type: "object", + properties: { + keyword: { type: "string", description: "可选,按目录名、路径、连接名或数据库名做关键词筛选" }, + connectionId: { type: "string", description: "可选,只看绑定到某个连接的外部 SQL 目录" }, + dbName: { type: "string", description: "可选,只看绑定到某个数据库的外部 SQL 目录" }, + limit: { type: "number", description: "可选,最多返回多少条目录,默认 20,最大 100" }, + }, + }, + }, + }, + }, { name: "inspect_active_tab", icon: "📍", diff --git a/frontend/src/utils/aiToolRegistry.test.ts b/frontend/src/utils/aiToolRegistry.test.ts index ddaa4b0..a10651b 100644 --- a/frontend/src/utils/aiToolRegistry.test.ts +++ b/frontend/src/utils/aiToolRegistry.test.ts @@ -66,6 +66,13 @@ describe('aiToolRegistry', () => { expect(info?.tool.function.description).toContain('本地已保存连接清单'); }); + it('registers the external-sql-directory inspector as a builtin tool', () => { + const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_external_sql_directories'); + expect(info).toBeTruthy(); + expect(info?.desc).toContain('外部 SQL 目录'); + expect(info?.tool.function.description).toContain('当前打开的外部 SQL 文件页签'); + }); + it('registers the saved-query and sql-snippet inspectors as builtin tools', () => { const savedQueryTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_saved_queries'); const aiSessionsTool = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_sessions'); @@ -104,6 +111,7 @@ describe('aiToolRegistry', () => { expect(tools.some((item) => item.function.name === 'inspect_current_connection')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_connection_capabilities')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_saved_connections')).toBe(true); + expect(tools.some((item) => item.function.name === 'inspect_external_sql_directories')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_saved_queries')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_ai_sessions')).toBe(true); expect(tools.some((item) => item.function.name === 'inspect_sql_snippets')).toBe(true);