feat(ai-tools): 新增AI配置体检探针并拆分本地快照执行器

- 新增 inspect_ai_setup_health 统一诊断供应商、聊天前置、MCP 接入和提示词技能状态
- 拆分 AI 配置类本地快照执行器与共享运行时类型,收缩 aiSnapshotInspectionToolExecutor 体积
- 补充内置工具目录、系统提示链路、定向测试与构建验证
This commit is contained in:
Syngnat
2026-06-09 04:56:30 +08:00
parent 6e7b8ceb39
commit 25fb3502e1
13 changed files with 714 additions and 126 deletions

View File

@@ -26,6 +26,8 @@ describe('AIBuiltinToolsCatalog', () => {
expect(markup).toContain('inspect_table_bundle');
expect(markup).toContain('全库快速摸底');
expect(markup).toContain('inspect_database_bundle');
expect(markup).toContain('一键体检 AI 配置');
expect(markup).toContain('inspect_ai_setup_health');
expect(markup).toContain('查看 AI 当前能力');
expect(markup).toContain('inspect_ai_runtime');
expect(markup).toContain('核对写入安全边界');

View File

@@ -37,6 +37,11 @@ const BUILTIN_TOOL_FLOWS = [
steps: 'inspect_database_bundle → inspect_table_bundle',
description: '适合先看整库有哪些表、每张表大概有哪些字段,再对目标表继续做深挖快照。',
},
{
title: '一键体检 AI 配置',
steps: 'inspect_ai_setup_health → inspect_ai_providers / inspect_mcp_setup / inspect_ai_guidance',
description: '适合先拿到一份 AI 配置健康快照看清当前是供应商没配好、聊天发送前置没满足、MCP 没接入,还是提示词 / Skills / 上下文还不完整,再决定往哪条探针继续下钻。',
},
{
title: '查看 AI 当前能力',
steps: 'inspect_ai_runtime → inspect_ai_context / inspect_current_connection',

View File

@@ -0,0 +1,115 @@
import { describe, expect, it, vi } from 'vitest';
import type { AIToolCall, SavedConnection } from '../../types';
import { executeLocalAIToolCall } from './aiLocalToolExecutor';
const buildConnection = (): SavedConnection => ({
id: 'conn-1',
name: '主库',
config: {
type: 'mysql',
host: '127.0.0.1',
port: 3306,
user: 'root',
},
});
const buildToolCall = (name: string, args: Record<string, unknown>): AIToolCall => ({
id: `call-${name}`,
type: 'function',
function: {
name,
arguments: JSON.stringify(args),
},
});
describe('aiLocalToolExecutor inspect_ai_setup_health', () => {
it('returns an actionable ai setup health snapshot for diagnosing provider, mcp, and guidance issues together', async () => {
const result = await executeLocalAIToolCall({
toolCall: buildToolCall('inspect_ai_setup_health', {}),
connections: [buildConnection()],
activeContext: {
connectionId: 'conn-1',
dbName: 'crm',
},
aiContexts: {
'conn-1:crm': [],
},
mcpTools: [],
skills: [{
id: 'skill-1',
name: '结构审查',
description: '优先核对字段',
systemPrompt: '先看字段和索引,再给结论。',
enabled: true,
scopes: ['database'],
requiredTools: ['get_columns'],
}],
userPromptSettings: {
global: '回答前先核对上下文。',
database: '',
jvm: '',
jvmDiagnostic: '',
},
dynamicModels: ['gpt-5.4', 'gpt-4.1'],
toolContextMap: new Map(),
runtime: {
getDatabases: vi.fn(),
getTables: vi.fn(),
getAIRuntimeState: vi.fn().mockResolvedValue({
activeProviderId: 'provider-1',
providers: [{
id: 'provider-1',
type: 'openai',
name: 'OpenAI 主账号',
apiKey: '',
hasSecret: true,
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-5.4',
models: ['gpt-5.4', 'gpt-4.1'],
maxTokens: 32000,
temperature: 0.2,
}],
safetyLevel: 'readonly',
contextLevel: 'schema_only',
}),
getMCPServers: vi.fn().mockResolvedValue([
{
id: 'server-1',
name: 'Browser',
transport: 'stdio',
command: 'uvx',
args: ['mcp-server-browser'],
env: {},
enabled: true,
timeoutSeconds: 20,
},
]),
getMCPClientInstallStatuses: vi.fn().mockResolvedValue([
{
client: 'codex',
displayName: 'Codex',
installed: false,
matchesCurrent: false,
clientDetected: true,
clientCommand: 'codex',
clientPath: 'C:/Tools/codex.exe',
configPath: 'C:/Users/demo/.codex/config.toml',
command: 'gonavi-mcp-server',
args: ['stdio'],
message: '未检测到 Codex 用户级 GoNavi MCP 配置',
},
]),
},
});
expect(result.success).toBe(true);
expect(result.content).toContain('"status":"needs_attention"');
expect(result.content).toContain('"activeProviderName":"OpenAI 主账号"');
expect(result.content).toContain('"chatStatus":"ready"');
expect(result.content).toContain('"enabledMCPServerCount":1');
expect(result.content).toContain('"currentExternalClientCount":0');
expect(result.content).toContain('如需让外部 CLI 使用 GoNavi MCP');
expect(result.content).toContain('当前聊天已就绪,但还没有挂载任何表结构上下文');
});
});

View File

@@ -1,6 +1,6 @@
import { DBGetAllColumns, DBGetDatabases, DBGetTables, ReadSQLFile } from '../../../wailsjs/go/app/App';
import type { AISnapshotInspectionRuntime } from './aiSnapshotInspectionToolExecutor';
import type { AISnapshotInspectionRuntime } from './aiSnapshotInspectionToolTypes';
export interface AIToolContextEntry {
connectionId: string;

View File

@@ -0,0 +1,134 @@
import { describe, expect, it } from 'vitest';
import { buildAISetupHealthSnapshot } from './aiSetupHealthInsights';
describe('buildAISetupHealthSnapshot', () => {
it('marks the setup as blocked when the active provider is missing critical pieces', () => {
const snapshot = buildAISetupHealthSnapshot({
providers: [{
id: 'provider-1',
type: 'openai',
name: 'OpenAI 主账号',
apiKey: '',
hasSecret: false,
baseUrl: '',
model: '',
models: [],
maxTokens: 32000,
temperature: 0.2,
}],
activeProviderId: 'provider-1',
builtinToolNames: ['inspect_ai_setup_health', 'inspect_ai_runtime'],
mcpServers: [],
mcpClientStatuses: [],
mcpTools: [],
skills: [],
dynamicModels: [],
userPromptSettings: {
global: '',
database: '',
jvm: '',
jvmDiagnostic: '',
},
activeContext: {
connectionId: 'conn-1',
dbName: 'crm',
},
activeContextItems: [],
});
expect(snapshot.status).toBe('blocked');
expect(snapshot.blockers).toContain('当前活动供应商缺少 API Key / Secret');
expect(snapshot.blockers).toContain('当前活动供应商缺少接口地址');
expect(snapshot.blockers).toContain('当前活动供应商还没有选中模型');
expect(snapshot.nextActions).toContain('补齐当前活动供应商的密钥');
expect(snapshot.nextActions).toContain('为当前活动供应商选择一个可用模型');
expect(snapshot.summary.chatReady).toBe(false);
});
it('summarizes a ready setup while still surfacing optional next-step guidance', () => {
const snapshot = buildAISetupHealthSnapshot({
providers: [{
id: 'provider-1',
type: 'openai',
name: 'OpenAI 主账号',
apiKey: '',
hasSecret: true,
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-5.4',
models: ['gpt-5.4', 'gpt-4.1'],
maxTokens: 32000,
temperature: 0.2,
}],
activeProviderId: 'provider-1',
safetyLevel: 'readonly',
contextLevel: 'with_samples',
builtinToolNames: ['inspect_ai_setup_health', 'inspect_ai_runtime', 'get_columns'],
mcpServers: [{
id: 'server-1',
name: 'Browser',
transport: 'stdio',
command: 'uvx',
args: ['mcp-server-browser'],
env: {},
enabled: true,
timeoutSeconds: 20,
}],
mcpClientStatuses: [{
client: 'codex',
displayName: 'Codex',
installed: true,
matchesCurrent: true,
clientDetected: true,
clientCommand: 'codex',
clientPath: 'C:/Tools/codex.exe',
configPath: 'C:/Users/demo/.codex/config.toml',
command: 'gonavi-mcp-server',
args: ['stdio'],
message: '已接入当前 GoNavi MCP',
}],
mcpTools: [{
alias: 'browser_open',
originalName: 'browser_open',
serverId: 'server-1',
serverName: 'Browser',
title: '打开页面',
description: '打开页面',
}],
skills: [{
id: 'skill-1',
name: '结构审查',
description: '优先核对字段',
systemPrompt: '先看字段和索引,再给结论。',
enabled: true,
scopes: ['database'],
requiredTools: ['get_columns'],
}],
dynamicModels: ['gpt-5.4', 'gpt-4.1'],
userPromptSettings: {
global: '回答前先核对上下文。',
database: '',
jvm: '',
jvmDiagnostic: '',
},
activeContext: {
connectionId: 'conn-1',
dbName: 'crm',
},
activeContextItems: [{
dbName: 'crm',
tableName: 'orders',
ddl: 'CREATE TABLE orders (...)',
}],
});
expect(snapshot.status).toBe('ready');
expect(snapshot.ready).toBe(true);
expect(snapshot.blockers).toHaveLength(0);
expect(snapshot.summary.activeProviderName).toBe('OpenAI 主账号');
expect(snapshot.summary.currentExternalClientCount).toBe(1);
expect(snapshot.summary.enabledSkillCount).toBe(1);
expect(snapshot.mcp.message).toContain('当前共配置 1 个 MCP 服务');
expect(snapshot.guidance.enabledSkillPreview).toContain('结构审查');
});
});

View File

@@ -0,0 +1,178 @@
import type {
AIContextItem,
AIMCPClientInstallStatus,
AIMCPServerConfig,
AIMCPToolDescriptor,
AIProviderConfig,
AISafetyLevel,
AISkillConfig,
AIUserPromptSettings,
} from '../../types';
import { buildAIChatReadinessSnapshot } from './aiChatReadiness';
import { buildAIGuidanceSnapshot } from './aiPromptInsights';
import { buildAIProviderSnapshot } from './aiProviderInsights';
import { buildAIRuntimeSnapshot } from './aiRuntimeInsights';
import { buildMCPSetupSnapshot } from './aiMCPInsights';
type AISetupHealthStatus = 'ready' | 'needs_attention' | 'blocked';
const appendUnique = (items: string[], value: string) => {
const trimmed = String(value || '').trim();
if (!trimmed || items.includes(trimmed)) {
return;
}
items.push(trimmed);
};
const summarizeSkillNames = (skills: Array<{ name?: string }>) =>
skills
.map((skill) => String(skill?.name || '').trim())
.filter(Boolean)
.slice(0, 6);
export const buildAISetupHealthSnapshot = (params: {
providers?: AIProviderConfig[];
activeProviderId?: string | null;
safetyLevel?: AISafetyLevel | string;
contextLevel?: string;
skills?: AISkillConfig[];
mcpServers?: AIMCPServerConfig[];
mcpClientStatuses?: AIMCPClientInstallStatus[];
mcpTools?: AIMCPToolDescriptor[];
dynamicModels?: string[];
builtinToolNames?: string[];
userPromptSettings?: AIUserPromptSettings;
activeContext?: { connectionId?: string | null; dbName?: string | null } | null;
activeContextItems?: AIContextItem[];
}) => {
const activeContextItems = Array.isArray(params.activeContextItems) ? params.activeContextItems : [];
const runtimeSnapshot = buildAIRuntimeSnapshot({
providers: params.providers,
activeProviderId: params.activeProviderId,
safetyLevel: params.safetyLevel,
contextLevel: params.contextLevel,
skills: params.skills,
mcpTools: params.mcpTools,
dynamicModels: params.dynamicModels,
builtinToolNames: params.builtinToolNames,
});
const providerSnapshot = buildAIProviderSnapshot({
providers: params.providers,
activeProviderId: params.activeProviderId,
dynamicModels: params.dynamicModels,
});
const chatReadiness = buildAIChatReadinessSnapshot({
providers: params.providers,
activeProviderId: params.activeProviderId,
dynamicModels: params.dynamicModels,
activeContext: params.activeContext,
activeContextItems,
});
const mcpSnapshot = buildMCPSetupSnapshot({
mcpServers: params.mcpServers,
mcpClientStatuses: params.mcpClientStatuses,
mcpTools: params.mcpTools,
});
const guidanceSnapshot = buildAIGuidanceSnapshot({
userPromptSettings: params.userPromptSettings,
skills: params.skills,
});
const blockers: string[] = [];
const warnings: string[] = [];
const nextActions: string[] = [];
if (!providerSnapshot.hasActiveProvider) {
appendUnique(blockers, '当前没有活动 AI 供应商');
appendUnique(nextActions, '先在 AI 设置里添加并选中一个活动供应商');
}
const activeProviderIssues = providerSnapshot.activeProvider?.issues || [];
if (activeProviderIssues.includes('missing_secret')) {
appendUnique(blockers, '当前活动供应商缺少 API Key / Secret');
appendUnique(nextActions, '补齐当前活动供应商的密钥');
}
if (activeProviderIssues.includes('missing_base_url')) {
appendUnique(blockers, '当前活动供应商缺少接口地址');
appendUnique(nextActions, '补齐当前活动供应商的 baseUrl');
}
if (activeProviderIssues.includes('missing_selected_model') || chatReadiness.status === 'missing_model') {
appendUnique(blockers, '当前活动供应商还没有选中模型');
appendUnique(nextActions, '为当前活动供应商选择一个可用模型');
}
if (chatReadiness.status === 'loading_models') {
appendUnique(warnings, '当前正在加载模型列表,模型选择尚未完成');
appendUnique(nextActions, '等待模型列表加载完成后重新确认活动模型');
}
if (mcpSnapshot.serverCount === 0) {
appendUnique(warnings, '当前还没有配置任何 MCP 服务');
appendUnique(nextActions, '如需扩展 AI 工具能力,可新增并测试至少 1 个 MCP 服务');
}
if (mcpSnapshot.currentClientCount === 0) {
appendUnique(warnings, 'Claude Code / Codex 还没有任何客户端接入当前 GoNavi MCP');
appendUnique(nextActions, '如需让外部 CLI 使用 GoNavi MCP先把当前 GoNavi 接入 Claude Code 或 Codex');
}
if (mcpSnapshot.enabledServerCount > 0 && runtimeSnapshot.mcpToolCount === 0) {
appendUnique(warnings, '已启用 MCP 服务,但当前还没有发现可用 MCP 工具');
appendUnique(nextActions, '逐条测试已启用的 MCP 服务,确认命令、参数和环境变量能正确发现工具');
}
if (guidanceSnapshot.customPromptCount === 0 && guidanceSnapshot.enabledSkillCount === 0) {
appendUnique(warnings, '当前没有自定义提示词或 Skills');
appendUnique(nextActions, '如需固定回答风格或工作流,可补充自定义提示词或启用 Skills');
}
if (chatReadiness.ready && params.activeContext?.connectionId && activeContextItems.length === 0) {
appendUnique(warnings, '当前聊天已就绪,但还没有挂载任何表结构上下文');
appendUnique(nextActions, '如需更准的 SQL / 结构建议,可先把目标表结构关联到 AI 上下文');
}
const status: AISetupHealthStatus = blockers.length > 0
? 'blocked'
: warnings.length > 0
? 'needs_attention'
: 'ready';
const message = status === 'ready'
? '当前 AI 配置体检通过,供应商、聊天前置和 MCP 运行链路都处于可用状态'
: status === 'blocked'
? `当前 AI 配置存在 ${blockers.length} 个阻塞项,优先修复活动供应商和聊天前置条件`
: `当前 AI 配置整体可用,但还有 ${warnings.length} 个建议项可以继续优化`;
return {
status,
ready: status === 'ready',
message,
blockers,
warnings,
nextActions,
summary: {
providerCount: providerSnapshot.providerCount,
readyProviderCount: providerSnapshot.readyProviderCount,
hasActiveProvider: providerSnapshot.hasActiveProvider,
activeProviderName: providerSnapshot.activeProvider?.name || providerSnapshot.activeProvider?.id || '',
chatStatus: chatReadiness.status,
chatReady: chatReadiness.ready,
safetyLevel: runtimeSnapshot.safetyLevel,
safetyLabel: runtimeSnapshot.safetyLabel,
contextLevel: runtimeSnapshot.contextLevel,
contextLabel: runtimeSnapshot.contextLabel,
enabledSkillCount: guidanceSnapshot.enabledSkillCount,
customPromptCount: guidanceSnapshot.customPromptCount,
mcpServerCount: mcpSnapshot.serverCount,
enabledMCPServerCount: mcpSnapshot.enabledServerCount,
installedExternalClientCount: mcpSnapshot.installedClientCount,
currentExternalClientCount: mcpSnapshot.currentClientCount,
discoveredMCPToolCount: mcpSnapshot.discoveredMCPToolCount,
totalAvailableToolCount: runtimeSnapshot.totalAvailableToolCount,
contextAttachedCount: chatReadiness.contextAttachedCount,
},
runtime: runtimeSnapshot,
provider: providerSnapshot,
chatReadiness,
mcp: mcpSnapshot,
guidance: {
...guidanceSnapshot,
enabledSkillPreview: summarizeSkillNames(guidanceSnapshot.enabledSkills),
},
};
};

View File

@@ -0,0 +1,191 @@
import type {
AIContextItem,
AIMCPToolDescriptor,
AISkillConfig,
AIUserPromptSettings,
SavedConnection,
TabData,
} from '../../types';
import { BUILTIN_AI_TOOL_INFO } from '../../utils/aiToolRegistry';
import { buildAIChatReadinessSnapshot } from './aiChatReadiness';
import { buildAIGuidanceSnapshot } from './aiPromptInsights';
import { buildAIProviderSnapshot } from './aiProviderInsights';
import { buildAIRuntimeSnapshot } from './aiRuntimeInsights';
import { buildAISafetySnapshot } from './aiSafetyInsights';
import { buildAISetupHealthSnapshot } from './aiSetupHealthInsights';
import { buildMCPSetupSnapshot } from './aiMCPInsights';
import type {
AISnapshotInspectionRuntime,
AISnapshotInspectionRuntimeState,
SnapshotInspectionResult,
} from './aiSnapshotInspectionToolTypes';
const BUILTIN_AI_TOOL_NAMES = BUILTIN_AI_TOOL_INFO.map((item) => item.name);
interface ExecuteAIConfigSnapshotToolCallOptions {
toolName: string;
activeContext?: { connectionId: string; dbName: string } | null;
aiContexts?: Record<string, AIContextItem[]>;
connections: SavedConnection[];
tabs?: TabData[];
activeTabId?: string | null;
mcpTools: AIMCPToolDescriptor[];
skills?: AISkillConfig[];
userPromptSettings?: AIUserPromptSettings;
dynamicModels?: string[];
runtime?: AISnapshotInspectionRuntime;
}
const loadRuntimeState = async (
runtime: AISnapshotInspectionRuntime | undefined,
): Promise<AISnapshotInspectionRuntimeState | undefined> =>
typeof runtime?.getAIRuntimeState === 'function'
? runtime.getAIRuntimeState()
: undefined;
const loadMCPSetupState = async (runtime: AISnapshotInspectionRuntime | undefined) =>
Promise.all([
typeof runtime?.getMCPServers === 'function' ? runtime.getMCPServers() : Promise.resolve(undefined),
typeof runtime?.getMCPClientInstallStatuses === 'function'
? runtime.getMCPClientInstallStatuses()
: Promise.resolve(undefined),
]);
export async function executeAIConfigSnapshotToolCall(
options: ExecuteAIConfigSnapshotToolCallOptions,
): Promise<SnapshotInspectionResult | null> {
const {
toolName,
activeContext = null,
aiContexts = {},
connections,
tabs = [],
activeTabId = null,
mcpTools,
skills = [],
userPromptSettings,
dynamicModels = [],
runtime,
} = options;
try {
switch (toolName) {
case 'inspect_ai_setup_health': {
const runtimeState = await loadRuntimeState(runtime);
const [mcpServers, mcpClientInstallStatuses] = await loadMCPSetupState(runtime);
const activeContextKey = activeContext?.connectionId
? `${activeContext.connectionId}:${activeContext.dbName || ''}`
: 'default';
return {
content: JSON.stringify(buildAISetupHealthSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
safetyLevel: runtimeState?.safetyLevel,
contextLevel: runtimeState?.contextLevel,
skills,
mcpServers: Array.isArray(mcpServers) ? mcpServers : [],
mcpClientStatuses: Array.isArray(mcpClientInstallStatuses) ? mcpClientInstallStatuses : [],
mcpTools,
dynamicModels,
builtinToolNames: BUILTIN_AI_TOOL_NAMES,
userPromptSettings,
activeContext,
activeContextItems: aiContexts[activeContextKey] || [],
})),
success: true,
};
}
case 'inspect_ai_runtime': {
const runtimeState = await loadRuntimeState(runtime);
return {
content: JSON.stringify(buildAIRuntimeSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
safetyLevel: runtimeState?.safetyLevel,
contextLevel: runtimeState?.contextLevel,
skills,
mcpTools,
dynamicModels,
builtinToolNames: BUILTIN_AI_TOOL_NAMES,
})),
success: true,
};
}
case 'inspect_ai_safety': {
const runtimeState = await loadRuntimeState(runtime);
return {
content: JSON.stringify(buildAISafetySnapshot({
safetyLevel: runtimeState?.safetyLevel,
activeContext,
tabs,
activeTabId,
connections,
})),
success: true,
};
}
case 'inspect_ai_providers': {
const runtimeState = await loadRuntimeState(runtime);
return {
content: JSON.stringify(buildAIProviderSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
dynamicModels,
})),
success: true,
};
}
case 'inspect_ai_chat_readiness': {
const runtimeState = await loadRuntimeState(runtime);
const activeContextKey = activeContext?.connectionId
? `${activeContext.connectionId}:${activeContext.dbName || ''}`
: 'default';
return {
content: JSON.stringify(buildAIChatReadinessSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
dynamicModels,
activeContext,
activeContextItems: aiContexts[activeContextKey] || [],
})),
success: true,
};
}
case 'inspect_mcp_setup': {
const [mcpServers, mcpClientInstallStatuses] = await loadMCPSetupState(runtime);
return {
content: JSON.stringify(buildMCPSetupSnapshot({
mcpServers: Array.isArray(mcpServers) ? mcpServers : [],
mcpClientStatuses: Array.isArray(mcpClientInstallStatuses) ? mcpClientInstallStatuses : [],
mcpTools,
})),
success: true,
};
}
case 'inspect_ai_guidance':
return {
content: JSON.stringify(buildAIGuidanceSnapshot({
userPromptSettings,
skills,
})),
success: true,
};
default:
return null;
}
} catch (error: any) {
const label = {
inspect_ai_setup_health: '体检当前 AI 配置失败',
inspect_ai_runtime: '读取当前 AI 运行状态失败',
inspect_ai_safety: '读取当前 AI 安全边界失败',
inspect_ai_providers: '读取当前 AI 供应商配置失败',
inspect_ai_chat_readiness: '读取 AI 聊天发送前置状态失败',
inspect_mcp_setup: '读取 MCP 配置状态失败',
inspect_ai_guidance: '读取当前 AI 提示与技能配置失败',
}[toolName] || '读取 AI 配置探针失败';
return {
content: `${label}: ${error?.message || error}`,
success: false,
};
}
}

View File

@@ -1,11 +1,7 @@
import type {
AIContextItem,
AIMCPClientInstallStatus,
AIMCPServerConfig,
AIMCPToolDescriptor,
AIChatMessage,
AIProviderConfig,
AISafetyLevel,
AISkillConfig,
AIUserPromptSettings,
ExternalSQLDirectory,
@@ -15,17 +11,10 @@ import type {
TabData,
} from '../../types';
import type { SqlLog } from '../../store';
import { BUILTIN_AI_TOOL_INFO } from '../../utils/aiToolRegistry';
import { buildAIContextSnapshot } from './aiContextInsights';
import { buildAIChatSessionsSnapshot } from './aiChatSessionInsights';
import { buildConnectionCapabilitiesSnapshot } from './aiConnectionCapabilitiesInsights';
import { buildCurrentConnectionSnapshot } from './aiConnectionInsights';
import { buildMCPSetupSnapshot } from './aiMCPInsights';
import { buildAIGuidanceSnapshot } from './aiPromptInsights';
import { buildAIChatReadinessSnapshot } from './aiChatReadiness';
import { buildAIProviderSnapshot } from './aiProviderInsights';
import { buildAIRuntimeSnapshot } from './aiRuntimeInsights';
import { buildAISafetySnapshot } from './aiSafetyInsights';
import {
buildSavedQueriesSnapshot,
buildSqlSnippetsSnapshot,
@@ -42,20 +31,11 @@ import {
buildActiveTabSnapshot,
buildWorkspaceTabsSnapshot,
} from './aiWorkspaceInsights';
export interface AISnapshotInspectionRuntimeState {
providers?: AIProviderConfig[];
activeProviderId?: string;
safetyLevel?: AISafetyLevel | string;
contextLevel?: string;
}
export interface AISnapshotInspectionRuntime {
getAIRuntimeState?: () => Promise<AISnapshotInspectionRuntimeState | undefined>;
getMCPServers?: () => Promise<AIMCPServerConfig[] | undefined>;
getMCPClientInstallStatuses?: () => Promise<AIMCPClientInstallStatus[] | undefined>;
readSQLFile?: (filePath: string) => Promise<any>;
}
import { executeAIConfigSnapshotToolCall } from './aiSnapshotInspectionAIConfigToolExecutor';
import type {
AISnapshotInspectionRuntime,
SnapshotInspectionResult,
} from './aiSnapshotInspectionToolTypes';
interface ExecuteSnapshotInspectionToolCallOptions {
toolName: string;
@@ -79,13 +59,6 @@ interface ExecuteSnapshotInspectionToolCallOptions {
runtime?: AISnapshotInspectionRuntime;
}
interface SnapshotInspectionResult {
content: string;
success: boolean;
}
const BUILTIN_AI_TOOL_NAMES = BUILTIN_AI_TOOL_INFO.map((item) => item.name);
export async function executeSnapshotInspectionToolCall(
options: ExecuteSnapshotInspectionToolCallOptions,
): Promise<SnapshotInspectionResult | null> {
@@ -112,93 +85,24 @@ export async function executeSnapshotInspectionToolCall(
} = options;
try {
const aiConfigResult = await executeAIConfigSnapshotToolCall({
toolName,
activeContext,
aiContexts,
connections,
tabs,
activeTabId,
mcpTools,
skills,
userPromptSettings,
dynamicModels,
runtime,
});
if (aiConfigResult) {
return aiConfigResult;
}
switch (toolName) {
case 'inspect_ai_runtime': {
const runtimeState = typeof runtime?.getAIRuntimeState === 'function'
? await runtime.getAIRuntimeState()
: undefined;
return {
content: JSON.stringify(buildAIRuntimeSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
safetyLevel: runtimeState?.safetyLevel,
contextLevel: runtimeState?.contextLevel,
skills,
mcpTools,
dynamicModels,
builtinToolNames: BUILTIN_AI_TOOL_NAMES,
})),
success: true,
};
}
case 'inspect_ai_safety': {
const runtimeState = typeof runtime?.getAIRuntimeState === 'function'
? await runtime.getAIRuntimeState()
: undefined;
return {
content: JSON.stringify(buildAISafetySnapshot({
safetyLevel: runtimeState?.safetyLevel,
activeContext,
tabs,
activeTabId,
connections,
})),
success: true,
};
}
case 'inspect_ai_providers': {
const runtimeState = typeof runtime?.getAIRuntimeState === 'function'
? await runtime.getAIRuntimeState()
: undefined;
return {
content: JSON.stringify(buildAIProviderSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
dynamicModels,
})),
success: true,
};
}
case 'inspect_ai_chat_readiness': {
const runtimeState = typeof runtime?.getAIRuntimeState === 'function'
? await runtime.getAIRuntimeState()
: undefined;
const activeContextKey = activeContext?.connectionId
? `${activeContext.connectionId}:${activeContext.dbName || ''}`
: 'default';
return {
content: JSON.stringify(buildAIChatReadinessSnapshot({
providers: Array.isArray(runtimeState?.providers) ? runtimeState.providers : [],
activeProviderId: runtimeState?.activeProviderId || '',
dynamicModels,
activeContext,
activeContextItems: aiContexts[activeContextKey] || [],
})),
success: true,
};
}
case 'inspect_mcp_setup': {
const [mcpServers, mcpClientInstallStatuses] = await Promise.all([
typeof runtime?.getMCPServers === 'function' ? runtime.getMCPServers() : Promise.resolve(undefined),
typeof runtime?.getMCPClientInstallStatuses === 'function' ? runtime.getMCPClientInstallStatuses() : Promise.resolve(undefined),
]);
return {
content: JSON.stringify(buildMCPSetupSnapshot({
mcpServers: Array.isArray(mcpServers) ? mcpServers : [],
mcpClientStatuses: Array.isArray(mcpClientInstallStatuses) ? mcpClientInstallStatuses : [],
mcpTools,
})),
success: true,
};
}
case 'inspect_ai_guidance':
return {
content: JSON.stringify(buildAIGuidanceSnapshot({
userPromptSettings,
skills,
})),
success: true,
};
case 'inspect_current_connection':
return {
content: JSON.stringify(buildCurrentConnectionSnapshot({
@@ -371,12 +275,6 @@ export async function executeSnapshotInspectionToolCall(
}
} catch (error: any) {
const label = {
inspect_ai_runtime: '读取当前 AI 运行状态失败',
inspect_ai_safety: '读取当前 AI 安全边界失败',
inspect_ai_providers: '读取当前 AI 供应商配置失败',
inspect_ai_chat_readiness: '读取 AI 聊天发送前置状态失败',
inspect_mcp_setup: '读取 MCP 配置状态失败',
inspect_ai_guidance: '读取当前 AI 提示与技能配置失败',
inspect_current_connection: '读取当前连接失败',
inspect_connection_capabilities: '读取当前连接能力矩阵失败',
inspect_saved_connections: '读取本地连接清单失败',

View File

@@ -0,0 +1,25 @@
import type {
AIMCPClientInstallStatus,
AIMCPServerConfig,
AIProviderConfig,
AISafetyLevel,
} from '../../types';
export interface AISnapshotInspectionRuntimeState {
providers?: AIProviderConfig[];
activeProviderId?: string;
safetyLevel?: AISafetyLevel | string;
contextLevel?: string;
}
export interface AISnapshotInspectionRuntime {
getAIRuntimeState?: () => Promise<AISnapshotInspectionRuntimeState | undefined>;
getMCPServers?: () => Promise<AIMCPServerConfig[] | undefined>;
getMCPClientInstallStatuses?: () => Promise<AIMCPClientInstallStatus[] | undefined>;
readSQLFile?: (filePath: string) => Promise<any>;
}
export interface SnapshotInspectionResult {
content: string;
success: boolean;
}

View File

@@ -68,13 +68,14 @@ 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_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'get_columns'],
availableToolNames: ['inspect_workspace_tabs', 'inspect_ai_setup_health', '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_external_sql_file', 'inspect_recent_sql_activity', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'get_columns'],
skills,
userPromptSettings,
});
const joined = messages.map((message) => message.content).join('\n');
expect(joined).toContain('inspect_workspace_tabs 盘点当前工作区');
expect(joined).toContain('inspect_ai_setup_health 先拿到整体现状');
expect(joined).toContain('inspect_ai_runtime 读取当前 AI 运行状态');
expect(joined).toContain('inspect_ai_safety 读取真实安全边界');
expect(joined).toContain('inspect_ai_providers 读取真实供应商配置');

View File

@@ -108,6 +108,19 @@ const appendAIRuntimeInspectionGuidance = (
});
};
const appendAISetupHealthInspectionGuidance = (
messages: AISystemContextMessage[],
availableToolNames: string[],
) => {
if (!availableToolNames.includes('inspect_ai_setup_health')) {
return;
}
messages.push({
role: 'system',
content: '如果用户提到“AI 为什么不好用”“帮我体检一下当前 AI 配置”“当前 AI 整体还有哪些明显问题”,优先调用 inspect_ai_setup_health 先拿到整体现状,再按需下钻 inspect_ai_providers、inspect_ai_chat_readiness、inspect_mcp_setup 或 inspect_ai_guidance。',
});
};
const appendAISafetyInspectionGuidance = (
messages: AISystemContextMessage[],
availableToolNames: string[],
@@ -405,6 +418,7 @@ SELECT * FROM users WHERE status = 1;
});
}
appendAIRuntimeInspectionGuidance(systemMessages, availableToolNames);
appendAISetupHealthInspectionGuidance(systemMessages, availableToolNames);
appendAISafetyInspectionGuidance(systemMessages, availableToolNames);
appendAIChatReadinessInspectionGuidance(systemMessages, availableToolNames);
appendAIProviderInspectionGuidance(systemMessages, availableToolNames);

View File

@@ -307,6 +307,23 @@ export const BUILTIN_AI_TOOL_INFO: AIBuiltinToolInfo[] = [
},
},
},
{
name: "inspect_ai_setup_health",
icon: "🩺",
desc: "一键体检当前 AI 配置健康度",
detail:
"汇总当前 AI 供应商、聊天发送前置、MCP 服务与外部客户端接入、提示词与 Skills、上下文挂载情况并给出阻塞项、告警项和下一步建议。适合用户说“AI 为什么不好用”“帮我看下 AI 整体有没有问题”“现在这套 AI 配置还缺什么”时先做一次总览诊断。",
params: "无参数",
tool: {
type: "function",
function: {
name: "inspect_ai_setup_health",
description:
"体检当前 AI 配置健康度返回供应商、模型、聊天发送前置、MCP 接入、提示词与 Skills、表结构上下文挂载等整体快照并给出阻塞项、建议项和下一步动作。适用于用户提到 AI 为什么不好用、当前 AI 配置哪里还缺、是否已经能稳定工作时,优先读取这份总览诊断,不要拆成多次猜测。",
parameters: { type: "object", properties: {} },
},
},
},
{
name: "inspect_ai_runtime",
icon: "🎛️",

View File

@@ -10,6 +10,13 @@ describe('aiToolRegistry', () => {
expect(info?.tool.function.description).toContain('当前供应商');
});
it('registers the ai-setup-health inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_setup_health');
expect(info).toBeTruthy();
expect(info?.desc).toContain('体检当前 AI 配置');
expect(info?.tool.function.description).toContain('聊天发送前置');
});
it('registers the ai-safety inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_safety');
expect(info).toBeTruthy();
@@ -113,6 +120,7 @@ describe('aiToolRegistry', () => {
}]);
expect(tools.some((item) => item.function.name === 'inspect_ai_runtime')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_setup_health')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_safety')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_providers')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_chat_readiness')).toBe(true);