feat: 实现 Tool Search Tool 动态工具发现机制
- 新增 ToolRegistry 工具注册表,支持核心工具和延迟加载工具分离 - 新增 tool_search 元工具,支持关键词搜索发现可用工具 - 新增基于关键词的搜索算法,按相关度评分排序 - 为所有工具添加 metadata(分类、关键词、延迟加载标识) - 修改 Agent 支持动态工具注入,tool_search 结果自动添加到可用工具 - 核心工具(tool_search, bash)始终加载,其他工具按需发现
This commit is contained in:
+121
-22
@@ -1,8 +1,16 @@
|
|||||||
import { createAnthropic } from '@ai-sdk/anthropic';
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
||||||
import { createDeepSeek } from '@ai-sdk/deepseek';
|
import { createDeepSeek } from '@ai-sdk/deepseek';
|
||||||
import { generateText, streamText, stepCountIs, type ModelMessage, type Tool as AITool, type LanguageModel } from 'ai';
|
import {
|
||||||
|
generateText,
|
||||||
|
streamText,
|
||||||
|
stepCountIs,
|
||||||
|
type ModelMessage,
|
||||||
|
type Tool as AITool,
|
||||||
|
type LanguageModel,
|
||||||
|
} from 'ai';
|
||||||
import type { Tool, ToolResult, Message, AgentConfig, ProviderType } from '../types/index.js';
|
import type { Tool, ToolResult, Message, AgentConfig, ProviderType } from '../types/index.js';
|
||||||
import { buildZodSchema } from '../types/index.js';
|
import { buildZodSchema } from '../types/index.js';
|
||||||
|
import { ToolRegistry } from '../tools/registry.js';
|
||||||
|
|
||||||
// Provider 工厂函数类型
|
// Provider 工厂函数类型
|
||||||
type ProviderFactory = (apiKey: string) => (model: string) => LanguageModel;
|
type ProviderFactory = (apiKey: string) => (model: string) => LanguageModel;
|
||||||
@@ -22,9 +30,17 @@ const providers: Record<ProviderType, ProviderFactory> = {
|
|||||||
export class Agent {
|
export class Agent {
|
||||||
private getModel: (model: string) => LanguageModel;
|
private getModel: (model: string) => LanguageModel;
|
||||||
private config: AgentConfig;
|
private config: AgentConfig;
|
||||||
private tools: Map<string, Tool> = new Map();
|
|
||||||
private conversationHistory: ModelMessage[] = [];
|
private conversationHistory: ModelMessage[] = [];
|
||||||
|
|
||||||
|
// 工具注册表
|
||||||
|
private registry: ToolRegistry | null = null;
|
||||||
|
|
||||||
|
// 已发现的工具(通过 tool_search 发现的)
|
||||||
|
private discoveredTools: Set<string> = new Set();
|
||||||
|
|
||||||
|
// 兼容旧模式:直接注册的工具
|
||||||
|
private legacyTools: Map<string, Tool> = new Map();
|
||||||
|
|
||||||
constructor(config: AgentConfig) {
|
constructor(config: AgentConfig) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
@@ -35,30 +51,67 @@ export class Agent {
|
|||||||
this.getModel = providerFactory(config.apiKey);
|
this.getModel = providerFactory(config.apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册单个工具
|
/**
|
||||||
registerTool(customTool: Tool): void {
|
* 设置工具注册表(新模式:支持动态工具发现)
|
||||||
this.tools.set(customTool.name, customTool);
|
*/
|
||||||
|
setRegistry(registry: ToolRegistry): void {
|
||||||
|
this.registry = registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量注册工具
|
/**
|
||||||
|
* 注册单个工具(兼容旧代码)
|
||||||
|
*/
|
||||||
|
registerTool(customTool: Tool): void {
|
||||||
|
this.legacyTools.set(customTool.name, customTool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量注册工具(兼容旧代码)
|
||||||
|
*/
|
||||||
registerTools(tools: Tool[]): void {
|
registerTools(tools: Tool[]): void {
|
||||||
for (const tool of tools) {
|
for (const tool of tools) {
|
||||||
this.tools.set(tool.name, tool);
|
this.legacyTools.set(tool.name, tool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将自定义工具转换为 Vercel AI SDK 的工具格式
|
/**
|
||||||
|
* 获取当前可用的工具
|
||||||
|
* - 如果使用 registry 模式:返回核心工具 + 已发现的工具
|
||||||
|
* - 如果使用旧模式:返回所有注册的工具
|
||||||
|
*/
|
||||||
|
private getAvailableTools(): Tool[] {
|
||||||
|
if (this.registry) {
|
||||||
|
// 新模式:核心工具 + 已发现的工具
|
||||||
|
const coreTools = this.registry.getCoreTools();
|
||||||
|
const discoveredTools = this.registry.getTools([...this.discoveredTools]);
|
||||||
|
return [...coreTools, ...discoveredTools];
|
||||||
|
} else {
|
||||||
|
// 旧模式:返回所有注册的工具
|
||||||
|
return [...this.legacyTools.values()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将工具转换为 Vercel AI SDK 的工具格式
|
||||||
|
*/
|
||||||
private getVercelTools(): Record<string, AITool> {
|
private getVercelTools(): Record<string, AITool> {
|
||||||
const vercelTools: Record<string, AITool> = {};
|
const vercelTools: Record<string, AITool> = {};
|
||||||
|
const availableTools = this.getAvailableTools();
|
||||||
|
|
||||||
for (const [name, customTool] of this.tools) {
|
for (const tool of availableTools) {
|
||||||
const schema = buildZodSchema(customTool.parameters);
|
const schema = buildZodSchema(tool.parameters);
|
||||||
|
|
||||||
vercelTools[name] = {
|
vercelTools[tool.name] = {
|
||||||
description: customTool.description,
|
description: tool.description,
|
||||||
inputSchema: schema,
|
inputSchema: schema,
|
||||||
execute: async (params) => {
|
execute: async (params) => {
|
||||||
const result = await customTool.execute(params as Record<string, unknown>);
|
const result = await tool.execute(params as Record<string, unknown>);
|
||||||
|
|
||||||
|
// 如果是 tool_search 调用,解析结果并注入发现的工具
|
||||||
|
if (tool.name === 'tool_search' && result.success) {
|
||||||
|
this.handleToolSearchResult(result.output);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
} as AITool;
|
} as AITool;
|
||||||
@@ -67,11 +120,25 @@ export class Agent {
|
|||||||
return vercelTools;
|
return vercelTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送消息并处理响应(流式)
|
/**
|
||||||
async chat(
|
* 处理 tool_search 的结果,将发现的工具添加到可用列表
|
||||||
userMessage: string,
|
*/
|
||||||
onStream?: (text: string) => void
|
private handleToolSearchResult(output: string): void {
|
||||||
): Promise<string> {
|
// 解析输出,提取工具名称
|
||||||
|
// 格式: "- tool_name: description [category]"
|
||||||
|
const matches = output.matchAll(/^- (\w+):/gm);
|
||||||
|
for (const match of matches) {
|
||||||
|
const toolName = match[1];
|
||||||
|
if (this.registry?.has(toolName)) {
|
||||||
|
this.discoveredTools.add(toolName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息并处理响应(流式)
|
||||||
|
*/
|
||||||
|
async chat(userMessage: string, onStream?: (text: string) => void): Promise<string> {
|
||||||
// 添加用户消息到历史
|
// 添加用户消息到历史
|
||||||
this.conversationHistory.push({
|
this.conversationHistory.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
@@ -97,7 +164,12 @@ export class Agent {
|
|||||||
const output = (chunk as { output?: ToolResult }).output;
|
const output = (chunk as { output?: ToolResult }).output;
|
||||||
if (output && typeof output === 'object') {
|
if (output && typeof output === 'object') {
|
||||||
if (output.success) {
|
if (output.success) {
|
||||||
onStream(`[结果: ${output.output}]\n`);
|
// 截断过长的输出
|
||||||
|
const displayOutput =
|
||||||
|
output.output.length > 500
|
||||||
|
? output.output.substring(0, 500) + '...(截断)'
|
||||||
|
: output.output;
|
||||||
|
onStream(`[结果: ${displayOutput}]\n`);
|
||||||
} else {
|
} else {
|
||||||
onStream(`[错误: ${output.error}]\n`);
|
onStream(`[错误: ${output.error}]\n`);
|
||||||
}
|
}
|
||||||
@@ -137,15 +209,21 @@ export class Agent {
|
|||||||
return fullResponse;
|
return fullResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空对话历史
|
/**
|
||||||
|
* 清空对话历史和发现的工具
|
||||||
|
*/
|
||||||
clearHistory(): void {
|
clearHistory(): void {
|
||||||
this.conversationHistory = [];
|
this.conversationHistory = [];
|
||||||
|
this.discoveredTools.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取对话历史
|
/**
|
||||||
|
* 获取对话历史
|
||||||
|
*/
|
||||||
getHistory(): Message[] {
|
getHistory(): Message[] {
|
||||||
return this.conversationHistory
|
return this.conversationHistory
|
||||||
.filter((msg): msg is ModelMessage & { role: 'user' | 'assistant' } =>
|
.filter(
|
||||||
|
(msg): msg is ModelMessage & { role: 'user' | 'assistant' } =>
|
||||||
msg.role === 'user' || msg.role === 'assistant'
|
msg.role === 'user' || msg.role === 'assistant'
|
||||||
)
|
)
|
||||||
.map((msg) => ({
|
.map((msg) => ({
|
||||||
@@ -153,4 +231,25 @@ export class Agent {
|
|||||||
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前可用工具的数量
|
||||||
|
*/
|
||||||
|
getToolCount(): { core: number; discovered: number; total: number } {
|
||||||
|
if (this.registry) {
|
||||||
|
const coreCount = this.registry.getCoreTools().length;
|
||||||
|
const discoveredCount = this.discoveredTools.size;
|
||||||
|
return {
|
||||||
|
core: coreCount,
|
||||||
|
discovered: discoveredCount,
|
||||||
|
total: coreCount + discoveredCount,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
core: this.legacyTools.size,
|
||||||
|
discovered: 0,
|
||||||
|
total: this.legacyTools.size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -4,7 +4,7 @@ import { Command } from 'commander';
|
|||||||
import { Agent } from './core/agent.js';
|
import { Agent } from './core/agent.js';
|
||||||
import { TerminalUI } from './ui/terminal.js';
|
import { TerminalUI } from './ui/terminal.js';
|
||||||
import { loadConfig, initConfig } from './utils/config.js';
|
import { loadConfig, initConfig } from './utils/config.js';
|
||||||
import { allTools } from './tools/index.js';
|
import { toolRegistry } from './tools/index.js';
|
||||||
import { getPermissionManager, promptPermission } from './permission/index.js';
|
import { getPermissionManager, promptPermission } from './permission/index.js';
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
@@ -37,8 +37,8 @@ program
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const agent = new Agent(config);
|
const agent = new Agent(config);
|
||||||
|
|
||||||
// 注册所有工具
|
// 设置工具注册表(支持动态工具发现)
|
||||||
agent.registerTools(allTools);
|
agent.setRegistry(toolRegistry);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await agent.chat(question, (text) => {
|
await agent.chat(question, (text) => {
|
||||||
@@ -60,8 +60,8 @@ program.action(async () => {
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const agent = new Agent(config);
|
const agent = new Agent(config);
|
||||||
|
|
||||||
// 注册所有工具
|
// 设置工具注册表(支持动态工具发现)
|
||||||
agent.registerTools(allTools);
|
agent.setRegistry(toolRegistry);
|
||||||
|
|
||||||
// 启动终端 UI
|
// 启动终端 UI
|
||||||
const ui = new TerminalUI(agent);
|
const ui = new TerminalUI(agent);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
@@ -18,9 +19,16 @@ async function copyRecursive(source: string, dest: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const copyFileTool: Tool = {
|
export const copyFileTool: ToolWithMetadata = {
|
||||||
name: 'copy_file',
|
name: 'copy_file',
|
||||||
description: loadDescription('copy_file'),
|
description: loadDescription('copy_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'copy_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '复制文件或目录',
|
||||||
|
keywords: ['copy', 'file', 'cp', 'duplicate', '复制', '文件', '拷贝'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
source: {
|
source: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const createDirectoryTool: Tool = {
|
export const createDirectoryTool: ToolWithMetadata = {
|
||||||
name: 'create_directory',
|
name: 'create_directory',
|
||||||
description: loadDescription('create_directory'),
|
description: loadDescription('create_directory'),
|
||||||
|
metadata: {
|
||||||
|
name: 'create_directory',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '创建目录',
|
||||||
|
keywords: ['create', 'directory', 'mkdir', 'folder', 'new', '创建', '目录', '文件夹', '新建'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const deleteFileTool: Tool = {
|
export const deleteFileTool: ToolWithMetadata = {
|
||||||
name: 'delete_file',
|
name: 'delete_file',
|
||||||
description: loadDescription('delete_file'),
|
description: loadDescription('delete_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'delete_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '删除文件或目录',
|
||||||
|
keywords: ['delete', 'remove', 'file', 'rm', '删除', '移除', '文件'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const editFileTool: Tool = {
|
export const editFileTool: ToolWithMetadata = {
|
||||||
name: 'edit_file',
|
name: 'edit_file',
|
||||||
description: loadDescription('edit_file'),
|
description: loadDescription('edit_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'edit_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '编辑文件内容(查找替换)',
|
||||||
|
keywords: ['edit', 'file', 'replace', 'modify', 'change', 'update', '编辑', '文件', '替换', '修改', '更新'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
@@ -45,9 +46,16 @@ function formatPermissions(mode: number): string {
|
|||||||
return `${fileType?.[1] || 'unknown'} (${perms})`;
|
return `${fileType?.[1] || 'unknown'} (${perms})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFileInfoTool: Tool = {
|
export const getFileInfoTool: ToolWithMetadata = {
|
||||||
name: 'get_file_info',
|
name: 'get_file_info',
|
||||||
description: loadDescription('get_file_info'),
|
description: loadDescription('get_file_info'),
|
||||||
|
metadata: {
|
||||||
|
name: 'get_file_info',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '获取文件元信息',
|
||||||
|
keywords: ['file', 'info', 'stat', 'size', 'permission', 'metadata', '文件', '信息', '大小', '权限', '属性'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
@@ -10,9 +11,16 @@ interface GrepMatch {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const grepContentTool: Tool = {
|
export const grepContentTool: ToolWithMetadata = {
|
||||||
name: 'grep_content',
|
name: 'grep_content',
|
||||||
description: loadDescription('grep_content'),
|
description: loadDescription('grep_content'),
|
||||||
|
metadata: {
|
||||||
|
name: 'grep_content',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '在文件内容中搜索文本',
|
||||||
|
keywords: ['grep', 'search', 'content', 'text', 'find', 'regex', '搜索', '内容', '文本', '查找', '正则'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
directory: {
|
directory: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const listDirTool: Tool = {
|
export const listDirTool: ToolWithMetadata = {
|
||||||
name: 'list_directory',
|
name: 'list_directory',
|
||||||
description: loadDescription('list_directory'),
|
description: loadDescription('list_directory'),
|
||||||
|
metadata: {
|
||||||
|
name: 'list_directory',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '列出目录内容',
|
||||||
|
keywords: ['list', 'directory', 'ls', 'dir', 'folder', '列出', '目录', '文件夹', '查看'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const moveFileTool: Tool = {
|
export const moveFileTool: ToolWithMetadata = {
|
||||||
name: 'move_file',
|
name: 'move_file',
|
||||||
description: loadDescription('move_file'),
|
description: loadDescription('move_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'move_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '移动或重命名文件/目录',
|
||||||
|
keywords: ['move', 'rename', 'file', 'mv', '移动', '重命名', '文件'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
source: {
|
source: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const readFileTool: Tool = {
|
export const readFileTool: ToolWithMetadata = {
|
||||||
name: 'read_file',
|
name: 'read_file',
|
||||||
description: loadDescription('read_file'),
|
description: loadDescription('read_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'read_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '读取文件内容',
|
||||||
|
keywords: ['read', 'file', 'content', 'cat', 'view', 'open', '读取', '文件', '内容', '查看', '打开'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const searchFilesTool: Tool = {
|
export const searchFilesTool: ToolWithMetadata = {
|
||||||
name: 'search_files',
|
name: 'search_files',
|
||||||
description: loadDescription('search_files'),
|
description: loadDescription('search_files'),
|
||||||
|
metadata: {
|
||||||
|
name: 'search_files',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '按文件名搜索文件',
|
||||||
|
keywords: ['search', 'file', 'find', 'glob', 'pattern', '搜索', '文件', '查找', '匹配'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
directory: {
|
directory: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
export const writeFileTool: Tool = {
|
export const writeFileTool: ToolWithMetadata = {
|
||||||
name: 'write_file',
|
name: 'write_file',
|
||||||
description: loadDescription('write_file'),
|
description: loadDescription('write_file'),
|
||||||
|
metadata: {
|
||||||
|
name: 'write_file',
|
||||||
|
category: 'filesystem',
|
||||||
|
description: '写入文件内容',
|
||||||
|
keywords: ['write', 'file', 'save', 'create', '写入', '文件', '保存', '创建', '新建'],
|
||||||
|
deferLoading: true,
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
+21
-14
@@ -1,8 +1,12 @@
|
|||||||
import type { Tool } from '../types/index.js';
|
import type { ToolWithMetadata } from './types.js';
|
||||||
|
import { toolRegistry } from './registry.js';
|
||||||
|
|
||||||
// Shell 工具
|
// Shell 工具
|
||||||
import { bashTool } from './shell/index.js';
|
import { bashTool } from './shell/index.js';
|
||||||
|
|
||||||
|
// 核心工具
|
||||||
|
import { toolSearchTool } from './tool-search.js';
|
||||||
|
|
||||||
// 文件系统工具
|
// 文件系统工具
|
||||||
import {
|
import {
|
||||||
readFileTool,
|
readFileTool,
|
||||||
@@ -18,30 +22,33 @@ import {
|
|||||||
deleteFileTool,
|
deleteFileTool,
|
||||||
} from './filesystem/index.js';
|
} from './filesystem/index.js';
|
||||||
|
|
||||||
// 所有可用工具的注册中心
|
// 所有工具列表(用于注册)
|
||||||
// 添加新工具只需在此数组中添加一行
|
const allToolsWithMetadata: ToolWithMetadata[] = [
|
||||||
export const allTools: Tool[] = [
|
// 核心工具 (deferLoading: false)
|
||||||
// Shell
|
toolSearchTool,
|
||||||
bashTool,
|
bashTool,
|
||||||
|
|
||||||
// 文件读写
|
// 文件系统工具 (deferLoading: true)
|
||||||
readFileTool,
|
readFileTool,
|
||||||
writeFileTool,
|
writeFileTool,
|
||||||
editFileTool,
|
editFileTool,
|
||||||
|
|
||||||
// 目录操作
|
|
||||||
listDirTool,
|
listDirTool,
|
||||||
createDirectoryTool,
|
createDirectoryTool,
|
||||||
|
|
||||||
// 搜索
|
|
||||||
searchFilesTool,
|
searchFilesTool,
|
||||||
grepContentTool,
|
grepContentTool,
|
||||||
|
|
||||||
// 文件信息
|
|
||||||
getFileInfoTool,
|
getFileInfoTool,
|
||||||
|
|
||||||
// 文件管理
|
|
||||||
moveFileTool,
|
moveFileTool,
|
||||||
copyFileTool,
|
copyFileTool,
|
||||||
deleteFileTool,
|
deleteFileTool,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 注册所有工具到 registry
|
||||||
|
toolRegistry.registerAll(allToolsWithMetadata);
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
export { toolRegistry } from './registry.js';
|
||||||
|
export { toolSearchTool } from './tool-search.js';
|
||||||
|
export type { ToolWithMetadata, ToolMetadata, ToolCategory, ToolSearchResult } from './types.js';
|
||||||
|
|
||||||
|
// 兼容旧代码:导出所有工具数组(基础 Tool 类型)
|
||||||
|
export const allTools = toolRegistry.getAllTools();
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import type { Tool } from '../types/index.js';
|
||||||
|
import type { ToolMetadata, ToolWithMetadata, ToolSearchResult } from './types.js';
|
||||||
|
import { searchTools } from './search.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具注册中心
|
||||||
|
* 管理所有工具的注册、查询和搜索
|
||||||
|
*/
|
||||||
|
class ToolRegistry {
|
||||||
|
private tools: Map<string, ToolWithMetadata> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册单个工具
|
||||||
|
*/
|
||||||
|
register(tool: ToolWithMetadata): void {
|
||||||
|
this.tools.set(tool.name, tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量注册工具
|
||||||
|
*/
|
||||||
|
registerAll(tools: ToolWithMetadata[]): void {
|
||||||
|
for (const tool of tools) {
|
||||||
|
this.register(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取核心工具 (deferLoading: false)
|
||||||
|
* 这些工具在会话开始时就可用
|
||||||
|
*/
|
||||||
|
getCoreTools(): Tool[] {
|
||||||
|
const coreTools: Tool[] = [];
|
||||||
|
for (const tool of this.tools.values()) {
|
||||||
|
if (!tool.metadata.deferLoading) {
|
||||||
|
coreTools.push(this.toBasicTool(tool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coreTools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定工具
|
||||||
|
*/
|
||||||
|
getTool(name: string): Tool | undefined {
|
||||||
|
const tool = this.tools.get(name);
|
||||||
|
return tool ? this.toBasicTool(tool) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取多个工具
|
||||||
|
*/
|
||||||
|
getTools(names: string[]): Tool[] {
|
||||||
|
const result: Tool[] = [];
|
||||||
|
for (const name of names) {
|
||||||
|
const tool = this.getTool(name);
|
||||||
|
if (tool) {
|
||||||
|
result.push(tool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索工具
|
||||||
|
* @param query 搜索查询
|
||||||
|
* @param limit 返回结果数量限制
|
||||||
|
* @returns 匹配的工具元数据列表
|
||||||
|
*/
|
||||||
|
search(query: string, limit: number = 5): ToolSearchResult[] {
|
||||||
|
const allMetadata = this.getAllMetadata();
|
||||||
|
return searchTools(query, allMetadata, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有工具的元数据
|
||||||
|
*/
|
||||||
|
getAllMetadata(): ToolMetadata[] {
|
||||||
|
return [...this.tools.values()].map((tool) => tool.metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有工具 (用于兼容旧代码)
|
||||||
|
*/
|
||||||
|
getAllTools(): Tool[] {
|
||||||
|
return [...this.tools.values()].map((tool) => this.toBasicTool(tool));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查工具是否存在
|
||||||
|
*/
|
||||||
|
has(name: string): boolean {
|
||||||
|
return this.tools.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工具数量
|
||||||
|
*/
|
||||||
|
get size(): number {
|
||||||
|
return this.tools.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 ToolWithMetadata 转换为基础 Tool 类型
|
||||||
|
*/
|
||||||
|
private toBasicTool(tool: ToolWithMetadata): Tool {
|
||||||
|
return {
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
parameters: tool.parameters,
|
||||||
|
execute: tool.execute,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export const toolRegistry = new ToolRegistry();
|
||||||
|
|
||||||
|
// 也导出类,方便测试
|
||||||
|
export { ToolRegistry };
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import type { ToolMetadata, ToolSearchResult } from './types.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分词函数,支持中英文
|
||||||
|
*/
|
||||||
|
function tokenize(text: string): string[] {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/[\s,,、_\-]+/)
|
||||||
|
.filter((t) => t.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算工具与查询的匹配分数
|
||||||
|
*/
|
||||||
|
function calculateScore(queryTerms: string[], tool: ToolMetadata): number {
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
const nameLower = tool.name.toLowerCase();
|
||||||
|
const descLower = tool.description.toLowerCase();
|
||||||
|
const keywordsLower = tool.keywords.map((k) => k.toLowerCase());
|
||||||
|
|
||||||
|
for (const term of queryTerms) {
|
||||||
|
// 名称精确匹配 (最高分)
|
||||||
|
if (nameLower === term) {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
// 名称包含匹配
|
||||||
|
else if (nameLower.includes(term)) {
|
||||||
|
score += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键词精确匹配
|
||||||
|
if (keywordsLower.includes(term)) {
|
||||||
|
score += 8;
|
||||||
|
}
|
||||||
|
// 关键词包含匹配
|
||||||
|
else if (keywordsLower.some((k) => k.includes(term) || term.includes(k))) {
|
||||||
|
score += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 描述包含匹配
|
||||||
|
if (descLower.includes(term)) {
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索工具
|
||||||
|
* @param query 搜索查询
|
||||||
|
* @param allTools 所有工具的元数据
|
||||||
|
* @param limit 返回结果数量限制
|
||||||
|
* @returns 匹配的工具列表(按分数排序)
|
||||||
|
*/
|
||||||
|
export function searchTools(
|
||||||
|
query: string,
|
||||||
|
allTools: ToolMetadata[],
|
||||||
|
limit: number = 5
|
||||||
|
): ToolSearchResult[] {
|
||||||
|
const queryTerms = tokenize(query);
|
||||||
|
|
||||||
|
if (queryTerms.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = allTools
|
||||||
|
// 只搜索延迟加载的工具
|
||||||
|
.filter((tool) => tool.deferLoading)
|
||||||
|
.map((tool) => ({
|
||||||
|
name: tool.name,
|
||||||
|
description: tool.description,
|
||||||
|
category: tool.category,
|
||||||
|
score: calculateScore(queryTerms, tool),
|
||||||
|
}))
|
||||||
|
.filter((r) => r.score > 0)
|
||||||
|
.sort((a, b) => b.score - a.score)
|
||||||
|
.slice(0, limit);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
+10
-2
@@ -1,14 +1,22 @@
|
|||||||
import { exec } from 'child_process';
|
import { exec } from 'child_process';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import type { Tool, ToolResult } from '../../types/index.js';
|
import type { ToolResult } from '../../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from '../types.js';
|
||||||
import { loadDescription } from '../load_description.js';
|
import { loadDescription } from '../load_description.js';
|
||||||
import { getPermissionManager } from '../../permission/index.js';
|
import { getPermissionManager } from '../../permission/index.js';
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
export const bashTool: Tool = {
|
export const bashTool: ToolWithMetadata = {
|
||||||
name: 'bash',
|
name: 'bash',
|
||||||
description: loadDescription('bash'),
|
description: loadDescription('bash'),
|
||||||
|
metadata: {
|
||||||
|
name: 'bash',
|
||||||
|
category: 'shell',
|
||||||
|
description: '执行 shell 命令',
|
||||||
|
keywords: ['bash', 'shell', 'command', 'execute', 'run', 'terminal', '命令', '执行', '终端', 'sh', 'cmd'],
|
||||||
|
deferLoading: false, // 核心工具,始终加载
|
||||||
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
command: {
|
command: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import type { ToolResult } from '../types/index.js';
|
||||||
|
import type { ToolWithMetadata } from './types.js';
|
||||||
|
import { toolRegistry } from './registry.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tool_search 工具
|
||||||
|
* 用于搜索可用的工具,实现动态工具发现
|
||||||
|
*/
|
||||||
|
export const toolSearchTool: ToolWithMetadata = {
|
||||||
|
name: 'tool_search',
|
||||||
|
description: `搜索可用的工具。当你需要执行某项任务但当前没有合适的工具时,使用此工具搜索。
|
||||||
|
|
||||||
|
可搜索的能力类别:
|
||||||
|
- 文件操作: 读取、写入、编辑、复制、移动、删除文件
|
||||||
|
- 目录操作: 列出目录、创建目录、搜索文件
|
||||||
|
- 内容搜索: 在文件中搜索文本、grep
|
||||||
|
- Shell: 执行命令行命令
|
||||||
|
- Git: 版本控制操作 (即将支持)
|
||||||
|
- 网络: HTTP请求、网页抓取 (即将支持)
|
||||||
|
|
||||||
|
搜索后返回的工具将可以直接使用。`,
|
||||||
|
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
type: 'string',
|
||||||
|
description: '描述你需要的功能,如 "读取文件内容"、"搜索代码"、"移动文件"、"执行命令"',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
metadata: {
|
||||||
|
name: 'tool_search',
|
||||||
|
category: 'core',
|
||||||
|
description: '搜索可用工具',
|
||||||
|
keywords: ['search', 'find', 'tool', 'discover', '搜索', '查找', '工具', '发现'],
|
||||||
|
deferLoading: false, // 核心工具,始终加载
|
||||||
|
},
|
||||||
|
|
||||||
|
execute: async (params: Record<string, unknown>): Promise<ToolResult> => {
|
||||||
|
const query = params.query as string;
|
||||||
|
|
||||||
|
if (!query || query.trim().length === 0) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
output: '',
|
||||||
|
error: '请提供搜索关键词',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = toolRegistry.search(query, 5);
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `没有找到与 "${query}" 匹配的工具。请尝试其他关键词,或使用更通用的描述。`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolList = results
|
||||||
|
.map((t) => `- ${t.name}: ${t.description} [${t.category}]`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
output: `找到 ${results.length} 个相关工具:\n\n${toolList}\n\n这些工具现在可以使用了。请选择合适的工具来完成任务。`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { ToolParameter, ToolResult } from '../types/index.js';
|
||||||
|
|
||||||
|
// 工具类别
|
||||||
|
export type ToolCategory = 'core' | 'filesystem' | 'shell' | 'git' | 'web' | 'database';
|
||||||
|
|
||||||
|
// 工具元数据
|
||||||
|
export interface ToolMetadata {
|
||||||
|
name: string;
|
||||||
|
category: ToolCategory;
|
||||||
|
description: string; // 简短描述,用于搜索结果展示
|
||||||
|
keywords: string[]; // 搜索关键词
|
||||||
|
deferLoading: boolean; // true = 延迟加载,false = 始终加载
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扩展后的工具定义(包含元数据)
|
||||||
|
export interface ToolWithMetadata {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
parameters: Record<string, ToolParameter>;
|
||||||
|
execute: (params: Record<string, unknown>) => Promise<ToolResult>;
|
||||||
|
metadata: ToolMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索结果
|
||||||
|
export interface ToolSearchResult {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category: ToolCategory;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user