- TaskStopTool: cancel running tasks via TaskManager.cancelTask() - TaskOutputTool: retrieve task results with status-aware formatting - SleepTool: Thread.sleep() with 5-minute max and interrupt support - Registered all 3 tools in AppConfig (total: 22 tools) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>pull/1/head
parent
08ebc72416
commit
d9b8c795b6
@ -0,0 +1,86 @@ |
||||
package com.claudecode.tool.impl; |
||||
|
||||
import com.claudecode.tool.Tool; |
||||
import com.claudecode.tool.ToolContext; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* Sleep 工具 —— 对应 claude-code/src/tools/SleepTool。 |
||||
* <p> |
||||
* 等待指定时长。用于暂停操作、等待外部进程、或用户要求休眠。 |
||||
* 支持通过中断取消等待。 |
||||
*/ |
||||
public class SleepTool implements Tool { |
||||
|
||||
private static final long MAX_DURATION_MS = 300_000; // 5 minutes max
|
||||
|
||||
@Override |
||||
public String name() { |
||||
return "Sleep"; |
||||
} |
||||
|
||||
@Override |
||||
public String description() { |
||||
return """ |
||||
Wait for a specified duration in milliseconds. The user can interrupt the sleep at \ |
||||
any time. Use this when: |
||||
- The user tells you to sleep or rest |
||||
- You have nothing to do and are waiting for something |
||||
- You need to wait for an external process to complete |
||||
Maximum duration: 300000ms (5 minutes)."""; |
||||
} |
||||
|
||||
@Override |
||||
public String inputSchema() { |
||||
return """ |
||||
{ |
||||
"type": "object", |
||||
"properties": { |
||||
"duration_ms": { |
||||
"type": "integer", |
||||
"description": "Duration to sleep in milliseconds (max: 300000)" |
||||
} |
||||
}, |
||||
"required": ["duration_ms"] |
||||
}"""; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isReadOnly() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public String execute(Map<String, Object> input, ToolContext context) { |
||||
Number durationNum = (Number) input.get("duration_ms"); |
||||
if (durationNum == null) { |
||||
return "Error: 'duration_ms' is required"; |
||||
} |
||||
|
||||
long durationMs = durationNum.longValue(); |
||||
if (durationMs <= 0) { |
||||
return "Error: duration_ms must be positive"; |
||||
} |
||||
if (durationMs > MAX_DURATION_MS) { |
||||
durationMs = MAX_DURATION_MS; |
||||
} |
||||
|
||||
long startTime = System.currentTimeMillis(); |
||||
try { |
||||
Thread.sleep(durationMs); |
||||
long elapsed = System.currentTimeMillis() - startTime; |
||||
return String.format("Slept for %d ms", elapsed); |
||||
} catch (InterruptedException e) { |
||||
long elapsed = System.currentTimeMillis() - startTime; |
||||
Thread.currentThread().interrupt(); |
||||
return String.format("Sleep interrupted after %d ms (requested %d ms)", elapsed, durationMs); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String activityDescription(Map<String, Object> input) { |
||||
Number ms = (Number) input.getOrDefault("duration_ms", 0); |
||||
return "💤 Sleeping " + (ms.longValue() / 1000.0) + "s"; |
||||
} |
||||
} |
||||
@ -0,0 +1,112 @@ |
||||
package com.claudecode.tool.impl; |
||||
|
||||
import com.claudecode.core.TaskManager; |
||||
import com.claudecode.core.TaskManager.TaskInfo; |
||||
import com.claudecode.tool.Tool; |
||||
import com.claudecode.tool.ToolContext; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
/** |
||||
* TaskOutput 工具 —— 对应 claude-code/src/tools/TaskOutputTool。 |
||||
* <p> |
||||
* 获取任务的执行输出/结果。当任务完成后,可以通过此工具读取其结果。 |
||||
* 对于正在运行的任务,返回当前状态信息。 |
||||
*/ |
||||
public class TaskOutputTool implements Tool { |
||||
|
||||
private static final String TASK_MANAGER_KEY = "TASK_MANAGER"; |
||||
|
||||
@Override |
||||
public String name() { |
||||
return "TaskOutput"; |
||||
} |
||||
|
||||
@Override |
||||
public String description() { |
||||
return """ |
||||
Get the output/result of a task. Use this to retrieve the result of a completed task, \ |
||||
or to check the current status of a running task. For completed tasks, returns the full \ |
||||
execution result. For running tasks, returns the current status."""; |
||||
} |
||||
|
||||
@Override |
||||
public String inputSchema() { |
||||
return """ |
||||
{ |
||||
"type": "object", |
||||
"properties": { |
||||
"task_id": { |
||||
"type": "string", |
||||
"description": "The ID of the task to get output from" |
||||
} |
||||
}, |
||||
"required": ["task_id"] |
||||
}"""; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isReadOnly() { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public String execute(Map<String, Object> input, ToolContext context) { |
||||
String taskId = (String) input.get("task_id"); |
||||
|
||||
if (taskId == null || taskId.isBlank()) { |
||||
return "Error: 'task_id' is required"; |
||||
} |
||||
|
||||
TaskManager taskManager = context.getOrDefault(TASK_MANAGER_KEY, null); |
||||
if (taskManager == null) { |
||||
return "Error: TaskManager is not available"; |
||||
} |
||||
|
||||
Optional<TaskInfo> taskOpt = taskManager.getTask(taskId); |
||||
if (taskOpt.isEmpty()) { |
||||
return "Error: Task not found: " + taskId; |
||||
} |
||||
|
||||
TaskInfo task = taskOpt.get(); |
||||
|
||||
return switch (task.status()) { |
||||
case COMPLETED -> { |
||||
String result = task.result(); |
||||
yield String.format(""" |
||||
{"task_id": "%s", "status": "COMPLETED", "description": "%s", "result": "%s"}""", |
||||
taskId, escapeJson(task.description()), |
||||
escapeJson(result != null ? result : "(no output)")); |
||||
} |
||||
case FAILED -> { |
||||
String error = task.result(); |
||||
yield String.format(""" |
||||
{"task_id": "%s", "status": "FAILED", "description": "%s", "error": "%s"}""", |
||||
taskId, escapeJson(task.description()), |
||||
escapeJson(error != null ? error : "(unknown error)")); |
||||
} |
||||
case CANCELLED -> String.format(""" |
||||
{"task_id": "%s", "status": "CANCELLED", "description": "%s"}""", |
||||
taskId, escapeJson(task.description())); |
||||
case RUNNING -> String.format(""" |
||||
{"task_id": "%s", "status": "RUNNING", "description": "%s", \ |
||||
"message": "Task is still running. Check back later."}""", |
||||
taskId, escapeJson(task.description())); |
||||
case PENDING -> String.format(""" |
||||
{"task_id": "%s", "status": "PENDING", "description": "%s", \ |
||||
"message": "Task has not started yet."}""", |
||||
taskId, escapeJson(task.description())); |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public String activityDescription(Map<String, Object> input) { |
||||
return "📋 Getting output of task " + input.getOrDefault("task_id", "..."); |
||||
} |
||||
|
||||
private String escapeJson(String s) { |
||||
if (s == null) return ""; |
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"); |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@ |
||||
package com.claudecode.tool.impl; |
||||
|
||||
import com.claudecode.core.TaskManager; |
||||
import com.claudecode.core.TaskManager.TaskInfo; |
||||
import com.claudecode.tool.Tool; |
||||
import com.claudecode.tool.ToolContext; |
||||
|
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
|
||||
/** |
||||
* TaskStop 工具 —— 对应 claude-code/src/tools/TaskStopTool。 |
||||
* <p> |
||||
* 停止一个正在运行的后台任务。通过 TaskManager.cancelTask() 取消任务执行。 |
||||
*/ |
||||
public class TaskStopTool implements Tool { |
||||
|
||||
private static final String TASK_MANAGER_KEY = "TASK_MANAGER"; |
||||
|
||||
@Override |
||||
public String name() { |
||||
return "TaskStop"; |
||||
} |
||||
|
||||
@Override |
||||
public String description() { |
||||
return """ |
||||
Stop a running background task by its ID. Use this tool when you need to terminate \ |
||||
a long-running task that is no longer needed, or when a task appears to be stuck. \ |
||||
Returns a success or failure status. Tasks in terminal states (COMPLETED/FAILED/CANCELLED) \ |
||||
cannot be stopped."""; |
||||
} |
||||
|
||||
@Override |
||||
public String inputSchema() { |
||||
return """ |
||||
{ |
||||
"type": "object", |
||||
"properties": { |
||||
"task_id": { |
||||
"type": "string", |
||||
"description": "The ID of the task to stop" |
||||
} |
||||
}, |
||||
"required": ["task_id"] |
||||
}"""; |
||||
} |
||||
|
||||
@Override |
||||
public String execute(Map<String, Object> input, ToolContext context) { |
||||
String taskId = (String) input.get("task_id"); |
||||
|
||||
if (taskId == null || taskId.isBlank()) { |
||||
return "Error: 'task_id' is required"; |
||||
} |
||||
|
||||
TaskManager taskManager = context.getOrDefault(TASK_MANAGER_KEY, null); |
||||
if (taskManager == null) { |
||||
return "Error: TaskManager is not available"; |
||||
} |
||||
|
||||
// Check if task exists first
|
||||
Optional<TaskInfo> taskOpt = taskManager.getTask(taskId); |
||||
if (taskOpt.isEmpty()) { |
||||
return "Error: Task not found: " + taskId; |
||||
} |
||||
|
||||
TaskInfo task = taskOpt.get(); |
||||
String previousStatus = task.status().name(); |
||||
|
||||
boolean cancelled = taskManager.cancelTask(taskId); |
||||
if (cancelled) { |
||||
return String.format(""" |
||||
{"status": "stopped", "task_id": "%s", "previous_status": "%s", \ |
||||
"description": "%s"}""", |
||||
taskId, previousStatus, escapeJson(task.description())); |
||||
} else { |
||||
return String.format( |
||||
"Error: Cannot stop task %s — it is already in terminal state: %s", |
||||
taskId, previousStatus); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String activityDescription(Map<String, Object> input) { |
||||
return "🛑 Stopping task " + input.getOrDefault("task_id", "..."); |
||||
} |
||||
|
||||
private String escapeJson(String s) { |
||||
if (s == null) return ""; |
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n"); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue