feat: Phase3 新增5个工具 (ListFiles, WebFetch, TodoWrite, Agent, NotebookEdit)
新增工具: - ListFilesTool: 目录列表,支持递归深度、隐藏文件过滤、文件大小显示 - WebFetchTool: HTTP内容获取,HTML→文本转换,大小限制100KB,超时30s - TodoWriteTool: 待办任务管理(add/update/complete/delete/list),内存存储 - AgentTool: 子Agent调用,通过ToolContext工厂方法创建独立AgentLoop - NotebookEditTool: Jupyter notebook编辑(read/insert/replace/delete/move) 配置更新: - AppConfig注册11个工具(原6+新5) - AgentLoop工厂方法注入ToolContext,支持AgentTool创建子Agent 工具总数: 6 → 11 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package com.claudecode.tool.impl;
|
||||
|
||||
import com.claudecode.tool.Tool;
|
||||
import com.claudecode.tool.ToolContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 子 Agent 工具 —— 对应 claude-code/src/tools/agent/AgentTool.ts。
|
||||
* <p>
|
||||
* 创建一个独立的子 Agent 来处理复杂的子任务。子 Agent 拥有独立的消息历史,
|
||||
* 但共享工具集和上下文环境。适用于:
|
||||
* <ul>
|
||||
* <li>需要独立上下文的子任务(如分析另一个文件)</li>
|
||||
* <li>并行处理多个任务</li>
|
||||
* <li>隔离风险操作</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 注意:子 Agent 使用主 Agent 的 ChatModel 和工具集,
|
||||
* 通过 ToolContext 中的 "agentLoop.factory" 获取 AgentLoop 工厂方法。
|
||||
*/
|
||||
public class AgentTool implements Tool {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AgentTool.class);
|
||||
|
||||
/** ToolContext 中存储 AgentLoop 工厂的键名 */
|
||||
public static final String AGENT_FACTORY_KEY = "__agent_factory__";
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "Agent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return """
|
||||
Launch a sub-agent to handle a complex task independently. \
|
||||
The sub-agent has its own conversation context but shares tools \
|
||||
and environment. Use this for tasks that require focused attention \
|
||||
or when you want to isolate a subtask. \
|
||||
The sub-agent will execute the given prompt and return its final response.""";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inputSchema() {
|
||||
return """
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The task description / prompt for the sub-agent"
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"description": "Additional context or instructions (optional)"
|
||||
}
|
||||
},
|
||||
"required": ["prompt"]
|
||||
}""";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(Map<String, Object> input, ToolContext context) {
|
||||
String prompt = (String) input.get("prompt");
|
||||
String additionalContext = (String) input.getOrDefault("context", "");
|
||||
|
||||
if (prompt == null || prompt.isBlank()) {
|
||||
return "Error: 'prompt' is required";
|
||||
}
|
||||
|
||||
// 从 ToolContext 获取 AgentLoop 工厂方法
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.function.Function<String, String> agentFactory =
|
||||
context.getOrDefault(AGENT_FACTORY_KEY, null);
|
||||
|
||||
if (agentFactory == null) {
|
||||
log.warn("AgentTool: 未配置 Agent 工厂,无法创建子 Agent");
|
||||
return "Error: Sub-agent capability is not configured. "
|
||||
+ "The Agent tool requires an agent factory to be registered in the ToolContext.";
|
||||
}
|
||||
|
||||
// 构建完整的子 Agent 提示
|
||||
String fullPrompt = buildSubAgentPrompt(prompt, additionalContext);
|
||||
|
||||
log.info("启动子 Agent,任务: {}", truncate(prompt, 80));
|
||||
|
||||
try {
|
||||
String result = agentFactory.apply(fullPrompt);
|
||||
log.info("子 Agent 完成,结果长度: {} chars", result.length());
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("子 Agent 执行失败", e);
|
||||
return "Error: Sub-agent failed: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建子 Agent 的完整提示词
|
||||
*/
|
||||
private String buildSubAgentPrompt(String prompt, String additionalContext) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("You are a sub-agent tasked with a specific job. ");
|
||||
sb.append("Complete the following task thoroughly and return your findings/results:\n\n");
|
||||
sb.append("## Task\n");
|
||||
sb.append(prompt);
|
||||
|
||||
if (additionalContext != null && !additionalContext.isBlank()) {
|
||||
sb.append("\n\n## Additional Context\n");
|
||||
sb.append(additionalContext);
|
||||
}
|
||||
|
||||
sb.append("\n\n## Instructions\n");
|
||||
sb.append("- Focus only on the given task\n");
|
||||
sb.append("- Use available tools as needed\n");
|
||||
sb.append("- Provide a clear, concise result\n");
|
||||
sb.append("- If the task cannot be completed, explain why\n");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String truncate(String text, int maxLen) {
|
||||
if (text.length() <= maxLen) return text;
|
||||
return text.substring(0, maxLen - 3) + "...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String activityDescription(Map<String, Object> input) {
|
||||
String prompt = (String) input.getOrDefault("prompt", "");
|
||||
if (prompt.length() > 40) {
|
||||
prompt = prompt.substring(0, 37) + "...";
|
||||
}
|
||||
return "🤖 Sub-agent: " + prompt;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user