package vip.mate.tool.builtin;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import vip.mate.agent.AgentService;
import vip.mate.agent.model.AgentEntity;
import vip.mate.agent.repository.AgentMapper;
import vip.mate.channel.web.ChatStreamTracker;
import vip.mate.workspace.conversation.ConversationService;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* 内置工具:Agent 委派(多 Agent 协作)
*
* 支持两种模式:
*
* - {@link #delegateToAgent} — 单任务委派(串行)
* - {@link #delegateParallel} — 多任务并行委派(最多 3 个子 Agent 同时执行)
*
* 被委派的 Agent 在独立子会话中运行(记录父子关系),
* 执行期间通过 SSE 事件 relay 向父会话实时推送进度。
* 子 Agent 的工具集自动收窄——禁止递归委派和 Agent 发现工具。
*
* @author MateClaw Team
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DelegateAgentTool {
private static final int MAX_DELEGATION_DEPTH = 3;
private static final int MAX_RESULT_LENGTH = 4000;
private static final int MAX_PARALLEL_CHILDREN = 3;
private static final int PARALLEL_TIMEOUT_SECONDS = 300; // 5 分钟
/** 子 Agent 禁用的工具:防递归 + 防副作用 */
private static final Set CHILD_DENIED_TOOLS = Set.of(
"delegateToAgent", // 禁止递归委派
"delegateParallel", // 禁止并行递归
"listAvailableAgents" // 子 Agent 不需要发现其他 Agent
);
/** 并行委派执行器:JDK 21 虚拟线程,每个子 Agent 一个轻量级虚拟线程 */
private static final ExecutorService DELEGATION_EXECUTOR =
Executors.newVirtualThreadPerTaskExecutor();
private final AgentService agentService;
private final AgentMapper agentMapper;
private final ChatStreamTracker streamTracker;
private final ConversationService conversationService;
private final ObjectMapper objectMapper;
// ==================== 单任务委派 ====================
@Tool(description = """
Delegate a task to another Agent for multi-agent collaboration. \
Target Agent executes in an independent session and returns its final reply. \
Parent receives real-time progress updates during execution. \
For multiple parallel tasks, use delegateParallel instead.""")
public String delegateToAgent(
@ToolParam(description = "Target Agent name (exact match)") String agentName,
@ToolParam(description = "Task description with complete context information") String task) {
if (agentName == null || agentName.isBlank()) {
return "[错误] 请指定目标 Agent 名称。" + availableAgentsHint();
}
if (task == null || task.isBlank()) {
return "[错误] 请提供任务描述。";
}
int depth = DelegationContext.currentDepth();
if (depth >= MAX_DELEGATION_DEPTH) {
return "[错误] 委派层级已达上限(" + MAX_DELEGATION_DEPTH + " 层),请直接处理任务。";
}
AgentEntity target = findAgent(agentName);
if (target == null) {
return "[错误] 未找到名为「" + agentName + "」的已启用 Agent。" + availableAgentsHint();
}
String parentConversationId = resolveParentConversationId();
String childConversationId = createChildConv(target, parentConversationId);
log.info("Agent 委派: depth={}, target={}({}), childConv={}, parentConv={}",
depth + 1, target.getName(), target.getId(), childConversationId, parentConversationId);
// SSE 广播 + relay
boolean hasParent = parentConversationId != null && streamTracker.isRunning(parentConversationId);
if (hasParent) {
streamTracker.broadcastObject(parentConversationId, "delegation_start", Map.of(
"childConversationId", childConversationId,
"childAgentName", target.getName(),
"task", truncate(task, 200)));
}
Runnable stopRelay = hasParent ? registerRelay(childConversationId, parentConversationId, target.getName()) : null;
// 执行
ChildResult result = runSingleChild(0, target, task, parentConversationId, childConversationId);
// 清理 + 广播结果
if (stopRelay != null) stopRelay.run();
if (hasParent) {
broadcastEnd(parentConversationId, childConversationId, target.getName(), result);
}
return result.toToolResponse(target.getName());
}
// ==================== 并行委派 ====================
@Tool(description = """
Delegate multiple tasks to different Agents in parallel (max 3). \
Each task runs concurrently in an independent child session. \
Use this when you have multiple independent sub-tasks that can run simultaneously. \
Input is a JSON array: [{"agentName":"Agent名称","task":"任务描述"}, ...]""")
public String delegateParallel(
@ToolParam(description = "JSON array of tasks: [{\"agentName\":\"X\",\"task\":\"Y\"}, ...]")
String tasksJson) {
// 1. 解析任务列表
List