Files
claude-code/src/main/java/com/claudecode/tool/impl/ToolSearchTool.java
T
abel533andCopilot 05e6e019b2 feat: BashTool sandbox, ConfigTool persistence, ToolSearchTool
- BashTool: command blacklist (fork bombs, rm -rf /, etc) + risky
  command warning log. Blocked commands return clear error message.
- ConfigTool: JSON file persistence (~/.claude-code/config.json),
  new 'list' action, loads from file on first access.
- ToolSearchTool: search ToolRegistry by name/description keywords,
  returns matching tools with descriptions (total: 23 tools).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-05 09:16:12 +08:00

116 lines
3.7 KiB
Java

package com.claudecode.tool.impl;
import com.claudecode.tool.Tool;
import com.claudecode.tool.ToolContext;
import com.claudecode.tool.ToolRegistry;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 工具搜索工具 —— 对应 claude-code 中 /tools 命令功能。
* <p>
* 在 ToolRegistry 中搜索已注册的工具,按名称或描述关键字匹配。
* 用于帮助 LLM 发现可用工具。
*/
public class ToolSearchTool implements Tool {
private static final String TOOL_REGISTRY_KEY = "TOOL_REGISTRY";
@Override
public String name() {
return "ToolSearch";
}
@Override
public String description() {
return """
Search for available tools by name or keyword. Returns matching tool names and \
their descriptions. Use this when you need to find which tools are available for \
a specific task, or when the user asks about available capabilities.""";
}
@Override
public String inputSchema() {
return """
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query to match against tool names and descriptions. Empty string lists all tools."
}
},
"required": ["query"]
}""";
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public String execute(Map<String, Object> input, ToolContext context) {
String query = (String) input.get("query");
if (query == null) query = "";
String queryLower = query.toLowerCase().trim();
ToolRegistry registry = context.getOrDefault(TOOL_REGISTRY_KEY, null);
if (registry == null) {
return "Error: ToolRegistry is not available";
}
List<Tool> allTools = registry.getTools();
List<Tool> matches;
if (queryLower.isEmpty()) {
matches = allTools;
} else {
matches = allTools.stream()
.filter(t -> t.name().toLowerCase().contains(queryLower)
|| t.description().toLowerCase().contains(queryLower))
.collect(Collectors.toList());
}
if (matches.isEmpty()) {
return String.format("""
{"query": "%s", "total_tools": %d, "matches": 0, \
"message": "No tools matched the query."}""",
escapeJson(query), allTools.size());
}
StringBuilder sb = new StringBuilder();
sb.append(String.format("Found %d tool(s)", matches.size()));
if (!queryLower.isEmpty()) {
sb.append(" matching \"").append(query).append("\"");
}
sb.append(" (").append(allTools.size()).append(" total):\n\n");
for (Tool t : matches) {
sb.append("• **").append(t.name()).append("**");
if (t.isReadOnly()) sb.append(" [read-only]");
sb.append("\n");
// Truncate description to first 120 chars for overview
String desc = t.description().strip();
if (desc.length() > 120) {
desc = desc.substring(0, 117) + "...";
}
sb.append(" ").append(desc).append("\n\n");
}
return sb.toString().stripTrailing();
}
@Override
public String activityDescription(Map<String, Object> input) {
return "🔍 Searching tools: " + input.getOrDefault("query", "*");
}
private String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"");
}
}