package com.easyagents.agent.runtime.agentscope;

import com.easyagents.agent.runtime.*;
import com.easyagents.agent.runtime.event.*;
import com.easyagents.agent.runtime.event.interceptor.AutoContextInterceptor;
import com.easyagents.agent.runtime.event.interceptor.MediaReferenceInterceptor;
import com.easyagents.agent.runtime.event.interceptor.ToolHitlInterceptor;
import com.easyagents.agent.runtime.event.observer.AgentRuntimeErrorObserver;
import com.easyagents.agent.runtime.event.observer.ReasoningLifecycleObserver;
import com.easyagents.agent.runtime.event.observer.SkillExecutionObserver;
import com.easyagents.agent.runtime.event.observer.ToolExecutionObserver;
import com.easyagents.agent.runtime.hitl.AgentPendingState;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalResolution;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException;
import com.easyagents.agent.runtime.knowledge.citation.AgentKnowledgeCitationMatcher;
import com.easyagents.agent.runtime.knowledge.citation.HeuristicKnowledgeCitationMatcher;
import com.easyagents.agent.runtime.message.*;
import com.easyagents.agent.runtime.mcp.McpRegistration;
import com.easyagents.agent.runtime.mcp.McpSkillRegistration;
import com.easyagents.agent.runtime.mcp.McpSpecValidator;
import com.easyagents.agent.runtime.mcp.McpToolkitAdapter;
import com.easyagents.agent.runtime.persistence.session.noop.NoopAgentSessionStore;
import com.easyagents.agent.runtime.prompt.SystemPromptComposer;
import com.easyagents.agent.runtime.skill.AgentSkillBinding;
import com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext;
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
import com.easyagents.agent.runtime.tool.AgentToolSpec;
import com.easyagents.agent.runtime.tool.operate.AgentOperateToolAdapter;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.agent.Event;
import io.agentscope.core.agent.EventType;
import io.agentscope.core.agent.StreamOptions;
import io.agentscope.core.memory.Memory;
import io.agentscope.core.memory.autocontext.AutoContextMemory;
import io.agentscope.core.message.*;
import io.agentscope.core.model.Model;
import io.agentscope.core.session.Session;
import io.agentscope.core.skill.SkillBox;
import io.agentscope.core.state.SessionKey;
import io.agentscope.core.tool.AgentTool;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.tool.mcp.McpClientWrapper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

/**
 * 基于 AgentScope 的有状态 ReAct 运行时。
 */
public class AgentScopeReActRuntime implements AgentRuntime {

    private final AgentScopeModelFactory modelFactory;
    private final AgentScopeToolAdapter toolAdapter;
    private final AgentScopeKnowledgeAdapter knowledgeAdapter;
    private final AgentScopeMemoryAdapter memoryAdapter;
    private final AgentScopeSkillAdapter skillAdapter;
    private final AgentScopeMessageAdapter messageAdapter;
    private final AgentOperateToolAdapter operateToolAdapter = new AgentOperateToolAdapter();
    private final McpToolkitAdapter mcpToolkitAdapter = new McpToolkitAdapter();
    private final AgentKnowledgeCitationMatcher citationMatcher = new HeuristicKnowledgeCitationMatcher();
    private final AtomicBoolean initialized = new AtomicBoolean(false);
    private final AtomicBoolean running = new AtomicBoolean(false);
    private AgentInitRequest initRequest;
    private AgentRuntimeExecutionContext runtimeContext;
    private AgentSkillRuntimeContext skillContext;
    private AgentToolApprovalCoordinator approvalCoordinator;
    private AgentRuntimeTurnContextHolder turnContextHolder;
    private Session session;
    private SessionKey sessionKey;
    private ReActAgent agent;
    private final List<McpClientWrapper> mcpClients = new CopyOnWriteArrayList<>();

    /**
     * 使用默认适配器创建运行时。
     */
    public AgentScopeReActRuntime() {
        this(new AgentScopeModelFactory(), new AgentScopeToolAdapter(), new AgentScopeKnowledgeAdapter(),
                new AgentScopeMemoryAdapter(), new AgentScopeSkillAdapter(), new AgentScopeMessageAdapter());
    }

    /**
     * 使用自定义适配器创建运行时。
     *
     * @param modelFactory 模型工厂
     * @param toolAdapter 工具适配器
     * @param knowledgeAdapter 知识库适配器
     * @param memoryAdapter 记忆适配器
     * @param skillAdapter Skill 适配器
     * @param messageAdapter 消息适配器
     */
    public AgentScopeReActRuntime(AgentScopeModelFactory modelFactory,
                                  AgentScopeToolAdapter toolAdapter,
                                  AgentScopeKnowledgeAdapter knowledgeAdapter,
                                  AgentScopeMemoryAdapter memoryAdapter,
                                  AgentScopeSkillAdapter skillAdapter,
                                  AgentScopeMessageAdapter messageAdapter) {
        this.modelFactory = modelFactory;
        this.toolAdapter = toolAdapter;
        this.knowledgeAdapter = knowledgeAdapter;
        this.memoryAdapter = memoryAdapter;
        this.skillAdapter = skillAdapter;
        this.messageAdapter = messageAdapter;
    }

    /**
     * 初始化有状态运行时。
     *
     * @param request 初始化请求
     */
    @Override
    public void init(AgentInitRequest request) {
        validateInitRequest(request);
        if (!initialized.compareAndSet(false, true)) {
            throw new AgentRuntimeException("Agent runtime has already been initialized.");
        }
        try {
            this.initRequest = request;
            this.runtimeContext = createRuntimeContext(request);
            this.skillContext = AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec());
            this.approvalCoordinator = AgentToolApprovalCoordinator.enabled();
            this.turnContextHolder = new AgentRuntimeTurnContextHolder();
            this.session = new AgentScopeSessionAdapter(request.getSessionStore());
            this.sessionKey = AgentScopeSessionAdapter.sessionKey(request.getSessionId());
            this.agent = buildAgent(runtimeContext);
            this.agent.loadIfExists(session, sessionKey);
        } catch (RuntimeException error) {
            closeMcpClients();
            initialized.set(false);
            throw error;
        }
    }

    /**
     * 关闭运行时并释放 MCP client。
     */
    @Override
    public void close() {
        if (approvalCoordinator != null) {
            approvalCoordinator.cancelAll("Agent runtime has been closed.");
        }
        closeMcpClients();
        initialized.set(false);
    }

    /**
     * <p>发送用户消息并流式输出运行事件</p>
     * <p>核心线路有两条，agent 交互的主线路与事件监察的旁线路
     * <ul>
     *     <li>主线路：agent 运行时事件，对接 agentscope 内部状态处理，只会进行极为克制的干扰</li>
     *     <li>旁线路：监察 agent 交互时的各类状态，提供给调用方和前端做状态展示，例如工具调用事件，审批事件等等</li>
     * </ul>
     * </p>
     * @param userMessage 用户消息
     * @return 运行事件流
     */
    @Override
    public Flux<AgentRuntimeEvent> stream(AgentMessage userMessage) {
        validateStreamRequest(userMessage);
        return Flux.defer(() -> runAgentStream(createTurnExecutionContext(userMessage), () -> buildInput(userMessage)));
    }

    /**
     * 恢复一次已挂起的运行。
     *
     * @param request 恢复请求
     * @return 运行事件流
     */
    @Override
    public Flux<AgentRuntimeEvent> resume(AgentResumeRequest request) {
        validateResumeRequest(request);
        return Flux.defer(() -> {
            if (!running.compareAndSet(false, true)) {
                return Flux.error(new AgentRuntimeException("Agent runtime is already streaming."));
            }
            AgentRuntimeExecutionContext executionContext = createResumeExecutionContext(request);
            AgentToolApprovalResolution resolution = null;
            try {
                if (request.isTrusted()) {
                    approvalCoordinator.authorizeTrustedExecution(request);
                } else {
                    resolution = approvalCoordinator.resolve(request);
                }
            } catch (RuntimeException error) {
                running.set(false);
                throw error;
            }
            if (resolution != null
                    && resolution.getStatus() == AgentToolApprovalResolution.Status.WAITING) {
                return waitingForRemainingApprovals(executionContext, resolution);
            }
            if (!request.isApproved()
                    || resolution != null
                    && (resolution.getStatus() == AgentToolApprovalResolution.Status.REJECTED
                    || resolution.getStatus() == AgentToolApprovalResolution.Status.EXPIRED)) {
                String cancelReason = resolution == null ? request.getRejectReason() : resolution.getReason();
                executionContext.setCancelReason(cancelReason);
                return Flux.defer(() -> {
                    saveSession();
                    return Flux.just(started(executionContext), cancelled(executionContext));
                }).doOnNext(event -> executionContext.getConversationRecorder().record(executionContext, event))
                        .doFinally(signalType -> cleanupStreamSegment(false));
            }
            return runAgentStreamAfterLock(executionContext, List::of);
        });
    }

    /**
     * 执行一轮 AgentScope 主线路并合并旁路事件。
     *
     * @param executionContext 本轮执行上下文
     * @param inputSupplier AgentScope 主线路输入供应器
     * @return 运行事件流
     */
    private Flux<AgentRuntimeEvent> runAgentStream(AgentRuntimeExecutionContext executionContext,
                                                   Supplier<List<Msg>> inputSupplier) {
        return Flux.defer(() -> {
            // 单个实例禁止并发 stream/resume，避免两轮对话同时读写 memory、session 和 turn context。
            if (!running.compareAndSet(false, true)) {
                return Flux.error(new AgentRuntimeException("Agent runtime is already streaming."));
            }
            // 新用户消息建立新的 Turn，上一 Turn 的 MCP 级批准不能跨轮复用。
            approvalCoordinator.clearReusableApprovalScopes();
            return runAgentStreamAfterLock(executionContext, inputSupplier);
        });
    }

    private Flux<AgentRuntimeEvent> runAgentStreamAfterLock(AgentRuntimeExecutionContext executionContext,
                                                            Supplier<List<Msg>> inputSupplier) {
        // 旁线路事件监听。Tool、Knowledge、AutoContext、Skill observer 都通过 bridge 写入这里。
        Sinks.Many<AgentRuntimeEvent> sideEvents = Sinks.many().unicast().onBackpressureBuffer();
        AgentRuntimeEventBridge eventBridge = new AgentRuntimeEventBridge(runtimeContext, turnContextHolder);
        // 将本轮上下文挂到 holder 后，adapter / observer 在 AgentScope 回调线程里也能拿到当前 requestId。
        turnContextHolder.set(new AgentRuntimeTurnContext(executionContext, sideEvents, eventBridge));
        // agent 输出的完整纯文本消息，用于知识库引注等。
        StringBuilder finalText = new StringBuilder();
        // agent 输出的完整结构化消息。
        AtomicReference<AgentMessage> finalMessage = new AtomicReference<>();
        // HITL 暂停事件。被设置后，本轮以 SUSPENDED 挂起而不是 COMPLETED 结束。
        AtomicReference<AgentRuntimeEvent> suspendedEvent = new AtomicReference<>();
        // 知识库引注。
        Map<String, AgentKnowledgeReference> knowledgeReferences = new LinkedHashMap<>();
        // 按 AgentScope 增量协议累计正文，并仅在终态快照到达时消除已发送前缀。
        StreamDeltaNormalizer deltaNormalizer = new StreamDeltaNormalizer();
        // 取消输出标记。
        AtomicBoolean cancelled = new AtomicBoolean(false);
        // 旁线路监察事件流式输出。
        Flux<AgentRuntimeEvent> sideEventFlux = sideEvents.asFlux()
                .doOnNext(event -> updateKnowledgeReferences(knowledgeReferences, event));
        // 主线路 agent 交互。resume 场景会传入空列表，让 AgentScope 从 pending tool 继续执行。
        Flux<AgentRuntimeEvent> mainEventFlux = agent.stream(inputSupplier.get(), streamOptions())
                .timeout(executionContext.getAgentDefinition().getExecutionOptions().getTimeout())
                // 保存输出完毕的完整消息
                .doOnNext(event -> captureFinalAgentResult(finalText, finalMessage, event))
                // 映射主线路事件：推理增量、文本增量和 HITL 暂停信号，并执行消息归一化。
                .flatMapIterable(event -> mapStreamEvent(executionContext, event))
                .map(deltaNormalizer::normalize)
                .doOnNext(event -> updateSuspendedEvent(suspendedEvent, event))
                .doOnNext(event -> updateFinalText(finalText, event))
                .doOnNext(event -> updateFinalMessage(finalMessage, event))
                // 输出完毕后持久化 memory/session/toolkit 等状态。
                .doOnComplete(this::saveSession)
                // 主线路结束后关闭旁路监听事件，让 merge 流可以自然完成。
                .doFinally(signalType -> sideEvents.tryEmitComplete())
                // 本次输出完毕。
                .concatWith(Flux.defer(() -> {
                    AgentRuntimeEvent suspended = suspendedEvent.get();
                    if (suspended != null) {
                        // SUSPENDED 已在主线路中输出，结束阶段不再重复发送。
                        return Flux.empty();
                    }
                    return Flux.just(completed(executionContext, finalText.toString(),
                            finalMessage.get(), knowledgeReferences));
                }));
        // STARTED 先发出；随后主线路和旁线路并行输出。
        return Flux.concat(Flux.just(started(executionContext)), Flux.merge(sideEventFlux, mainEventFlux))
                .onErrorResume(error -> errorEventFlux(executionContext, error))
                // 输出所有事件统一交给调用方存储事件记录，默认是空实现即不记录。
                .doOnNext(event -> executionContext.getConversationRecorder().record(executionContext, event))
                // 处理中断请求
                .doOnCancel(() -> cancelInternal(executionContext, sideEvents, finalText, finalMessage, cancelled))
                // HITL 挂起时保留当前 Turn 的 MCP 批准，其余终态完整清理。
                .doFinally(signalType -> cleanupStreamSegment(suspendedEvent.get() != null));
    }

    /**
     * 校验流式运行的必要条件。
     *
     * @param userMessage 用户消息
     */
    private void validateStreamRequest(AgentMessage userMessage) {
        if (!initialized.get() || agent == null) {
            throw new AgentRuntimeException("Agent runtime has not been initialized.");
        }
        if (userMessage == null) {
            throw new AgentRuntimeException("Agent user message is required.");
        }
        if (userMessage.getContentBlocks() == null || userMessage.getContentBlocks().isEmpty()) {
            throw new AgentRuntimeException("Agent user message content is required.");
        }
        boolean containsImage = userMessage.getContentBlocks().stream()
                .anyMatch(block -> block instanceof AgentMediaBlock mediaBlock
                        && "image".equalsIgnoreCase(mediaBlock.getMediaKind()));
        if (containsImage && !initRequest.getAgentDefinition().getModelSpec().isSupportImage()) {
            throw new AgentRuntimeException("The configured model does not support image input.");
        }
    }

    /**
     * 校验恢复请求的必要条件。
     *
     * @param request 恢复请求
     */
    private void validateResumeRequest(AgentResumeRequest request) {
        if (!initialized.get() || agent == null) {
            throw new AgentRuntimeException("Agent runtime has not been initialized.");
        }
        if (request == null) {
            throw new AgentRuntimeException("Agent resume request is required.");
        }
        if (request.getResumeToken() == null
                || request.getResumeToken().getValue() == null
                || request.getResumeToken().getValue().isBlank()) {
            throw new AgentRuntimeException("Agent resume token is required.");
        }
    }

    /**
     * 创建单轮运行上下文。
     *
     * @param userMessage 用户消息
     * @return 单轮运行上下文
     */
    private AgentRuntimeExecutionContext createTurnExecutionContext(AgentMessage userMessage) {
        AgentRuntimeExecutionContext context = new AgentRuntimeExecutionContext();
        context.setRequestId(UUID.randomUUID().toString());
        context.setTraceId(runtimeContext.getTraceId());
        context.setSessionId(runtimeContext.getSessionId());
        context.setAgentDefinition(runtimeContext.getAgentDefinition());
        context.setRuntimeContext(runtimeContext.getRuntimeContext());
        context.setUserMessage(userMessage);
        context.setToolInvokers(runtimeContext.getToolInvokers());
        context.setKnowledgeRegistrations(runtimeContext.getKnowledgeRegistrations());
        context.setSessionStore(runtimeContext.getSessionStore());
        context.setConversationRecorder(runtimeContext.getConversationRecorder());
        context.setMetadata(runtimeContext.getMetadata());
        return context;
    }

    /**
     * 创建恢复运行上下文。
     *
     * @param request 恢复请求
     * @return 恢复运行上下文
     */
    private AgentRuntimeExecutionContext createResumeExecutionContext(AgentResumeRequest request) {
        AgentRuntimeExecutionContext context = new AgentRuntimeExecutionContext();
        context.setRequestId(UUID.randomUUID().toString());
        context.setTraceId(runtimeContext.getTraceId());
        context.setSessionId(runtimeContext.getSessionId());
        context.setAgentDefinition(runtimeContext.getAgentDefinition());
        context.setRuntimeContext(runtimeContext.getRuntimeContext());
        context.setToolInvokers(runtimeContext.getToolInvokers());
        context.setKnowledgeRegistrations(runtimeContext.getKnowledgeRegistrations());
        context.setSessionStore(runtimeContext.getSessionStore());
        context.setConversationRecorder(runtimeContext.getConversationRecorder());
        Map<String, Object> metadata = new LinkedHashMap<>(runtimeContext.getMetadata());
        metadata.putAll(request.getMetadata());
        metadata.put("resume", true);
        metadata.put("resumeToken", request.getResumeToken().getValue());
        context.setMetadata(metadata);
        return context;
    }

    /**
     * 构建传入 AgentScope 主线路的用户输入。
     *
     * <p>这里只传当前用户消息。历史上下文、AutoContext 压缩和 session 状态均由
     * AgentScope memory/session 主线路负责管理。</p>
     *
     * @param userMessage 用户消息
     * @return AgentScope 输入消息
     */
    private List<Msg> buildInput(AgentMessage userMessage) {
        Msg message = messageAdapter.toMsg(userMessage);
        return message == null ? List.of() : List.of(message);
    }

    /**
     * 构建 AgentScope 流式选项。
     *
     * @return 流式选项
     */
    private StreamOptions streamOptions() {
        return StreamOptions.builder()
                .eventTypes(EventType.ALL)
                .incremental(true)
                .includeReasoningChunk(runtimeContext.getAgentDefinition().getExecutionOptions().isReasoningEnabled())
                .includeReasoningResult(false)
                .includeActingChunk(true)
                .includeSummaryChunk(true)
                .includeSummaryResult(true)
                .build();
    }

    /**
     * 将 AgentScope 主线路事件映射为 Easy-Agents 运行时事件。
     *
     * <p>这里仅处理 AgentScope {@code agent.stream(...)} 主线路中需要展示给调用方的
     * 模型内容。工具、知识库、AutoContext 和 Skill 状态都由旁路 bridge 通过
     * observer/interceptor 发出，不参与这里的 AgentScope 主输出映射。</p>
     *
     * @param context 本轮运行上下文
     * @param event AgentScope 主线路事件
     * @return 运行时事件列表
     */
    private List<AgentRuntimeEvent> mapStreamEvent(AgentRuntimeExecutionContext context, Event event) {
        if (event == null) {
            return List.of();
        }
        if (event.getType() == EventType.REASONING) {
            return mapReasoningEvent(context, event);
        }
        if (event.getType() == EventType.SUMMARY) {
            return mapSummaryEvent(context, event);
        }
        if (event.getType() == EventType.AGENT_RESULT) {
            return mapAgentResultEvent(context, event);
        }
        // HINT 属于 AgentScope 注入给模型的上下文提示，不作为聊天主输出展示。
        return List.of();
    }

    /**
     * 映射 AgentScope reasoning chunk。
     *
     * @param context 本轮运行上下文
     * @param event AgentScope reasoning 事件
     * @return 运行时事件列表
     */
    private List<AgentRuntimeEvent> mapReasoningEvent(AgentRuntimeExecutionContext context, Event event) {
        if (event.isLast() || event.getMessage() == null || event.getMessage().getContent() == null) {
            return List.of();
        }
        List<AgentRuntimeEvent> events = new ArrayList<>();
        List<ContentBlock> blocks = event.getMessage().getContent();
        for (int blockIndex = 0; blockIndex < blocks.size(); blockIndex++) {
            ContentBlock block = blocks.get(blockIndex);
            if (block instanceof ThinkingBlock thinkingBlock) {
                AgentRuntimeEvent runtimeEvent = base(context, AgentRuntimeEventType.REASONING_DELTA);
                runtimeEvent.setMessageId(event.getMessageId());
                runtimeEvent.getPayload().put("reasoning", thinkingBlock.getThinking());
                runtimeEvent.getPayload().put("last", event.isLast());
                runtimeEvent.getMetadata().put("source", "AGENTSCOPE_STREAM");
                runtimeEvent.getMetadata().put("blockIndex", blockIndex);
                runtimeEvent.getMetadata().putAll(nullToEmpty(thinkingBlock.getMetadata()));
                events.add(runtimeEvent);
                continue;
            }
            if (block instanceof TextBlock textBlock) {
                AgentRuntimeEvent runtimeEvent = base(context, AgentRuntimeEventType.MESSAGE_DELTA);
                runtimeEvent.setMessageId(event.getMessageId());
                runtimeEvent.getPayload().put("text", textBlock.getText());
                runtimeEvent.getPayload().put("last", event.isLast());
                runtimeEvent.getMetadata().put("source", "AGENTSCOPE_STREAM");
                runtimeEvent.getMetadata().put("blockIndex", blockIndex);
                events.add(runtimeEvent);
                continue;
            }
        }
        return events;
    }

    /**
     * 映射 AgentScope summary chunk。
     *
     * @param context 本轮运行上下文
     * @param event AgentScope summary 事件
     * @return 运行时事件列表
     */
    private List<AgentRuntimeEvent> mapSummaryEvent(AgentRuntimeExecutionContext context, Event event) {
        if (event.getMessage() == null || event.getMessage().getContent() == null) {
            return List.of();
        }
        List<AgentRuntimeEvent> events = new ArrayList<>();
        List<ContentBlock> blocks = event.getMessage().getContent();
        for (int blockIndex = 0; blockIndex < blocks.size(); blockIndex++) {
            ContentBlock block = blocks.get(blockIndex);
            if (block instanceof TextBlock textBlock) {
                AgentRuntimeEvent runtimeEvent = base(context, AgentRuntimeEventType.MESSAGE_DELTA);
                runtimeEvent.setMessageId(event.getMessageId());
                runtimeEvent.getPayload().put("text", textBlock.getText());
                runtimeEvent.getPayload().put("last", event.isLast());
                runtimeEvent.getPayload().put("summary", true);
                runtimeEvent.getMetadata().put("source", "AGENTSCOPE_STREAM");
                runtimeEvent.getMetadata().put("blockIndex", blockIndex);
                runtimeEvent.getMetadata().put("streamType", "SUMMARY");
                events.add(runtimeEvent);
            }
        }
        return events;
    }

    /**
     * 映射 AgentScope 最终结果事件。
     *
     * <p>普通最终结果只用于后端收口，不作为 {@link AgentRuntimeEventType#MESSAGE_DELTA}
     * 下发给前端。HITL 暂停是例外：AgentScope 用最终结果事件携带
     * {@link GenerateReason#REASONING_STOP_REQUESTED}，这里需要转换为 Easy-Agents
     * 的 {@link AgentRuntimeEventType#SUSPENDED}。</p>
     *
     * @param context 本轮运行上下文
     * @param event AgentScope 最终结果事件
     * @return 运行时事件列表
     */
    private List<AgentRuntimeEvent> mapAgentResultEvent(AgentRuntimeExecutionContext context, Event event) {
        if (event.getMessage() != null
                && event.getMessage().getGenerateReason() == GenerateReason.REASONING_STOP_REQUESTED) {
            return List.of(suspended(context, event));
        }
        return List.of();
    }

    /**
     * 生成暂停事件。
     *
     * <p>该事件表示 AgentScope 主线路被 HITL interceptor 主动暂停并挂起，等待外部审批后恢复。</p>
     *
     * @param context 本轮运行上下文
     * @param sourceEvent AgentScope 结果事件
     * @return 暂停事件
     */
    private AgentRuntimeEvent suspended(AgentRuntimeExecutionContext context, Event sourceEvent) {
        AgentRuntimeEvent event = base(context, AgentRuntimeEventType.SUSPENDED);
        event.setMessageId(sourceEvent.getMessageId());
        if (sourceEvent.getMessage() != null) {
            event.setMessage(messageAdapter.toAgentMessage(sourceEvent.getMessage()));
        }
        event.getPayload().put("reason", "TOOL_APPROVAL_REQUIRED");
        event.getPayload().put("pendingApprovals", approvalCoordinator.pendingStates(context.getSessionId()).stream()
                .map(this::pendingApprovalPayload)
                .toList());
        event.getMetadata().put("source", "AGENTSCOPE_STREAM");
        event.getMetadata().put("generateReason", sourceEvent.getMessage() == null
                ? GenerateReason.REASONING_STOP_REQUESTED.name()
                : sourceEvent.getMessage().getGenerateReason().name());
        return event;
    }

    /**
     * 在同一审批批次仍有未决工具时返回挂起事件，并保持 AgentScope pending tools 不执行。
     *
     * @param context 本轮恢复上下文
     * @param resolution 审批批次决议
     * @return 开始与挂起事件流
     */
    private Flux<AgentRuntimeEvent> waitingForRemainingApprovals(AgentRuntimeExecutionContext context,
                                                                AgentToolApprovalResolution resolution) {
        AgentRuntimeEvent suspended = base(context, AgentRuntimeEventType.SUSPENDED);
        suspended.getPayload().put("reason", "TOOL_APPROVAL_REQUIRED");
        suspended.getPayload().put("pendingApprovals", resolution.getRemainingStates().stream()
                .map(this::pendingApprovalPayload)
                .toList());
        suspended.getMetadata().put("source", "APPROVAL_COORDINATOR");
        suspended.getMetadata().put("approvalStatus", AgentToolApprovalResolution.Status.WAITING.name());
        return Flux.just(started(context), suspended)
                .doOnNext(event -> context.getConversationRecorder().record(context, event))
                .doFinally(signalType -> cleanupStreamSegment(true));
    }

    /**
     * 将待审批状态转换为前端可消费的稳定字段。
     *
     * @param state 待审批状态
     * @return 待审批载荷
     */
    private Map<String, Object> pendingApprovalPayload(AgentPendingState state) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("resumeToken", state.getResumeToken().getValue());
        payload.put("toolCallId", state.getToolCallId());
        payload.put("toolName", state.getToolName());
        payload.put("toolInput", state.getToolInput());
        payload.put("input", state.getToolInput());
        payload.put("approvalPrompt", state.getApprovalPrompt());
        payload.put("approvalMetadata", state.getMetadata());
        payload.put("expiresAt", state.getExpiresAt() == null ? null : state.getExpiresAt().toString());
        return payload;
    }

    /**
     * 生成开始事件。
     *
     * @param context 本轮运行上下文
     * @return 开始事件
     */
    private AgentRuntimeEvent started(AgentRuntimeExecutionContext context) {
        AgentRuntimeEvent event = base(context, AgentRuntimeEventType.STARTED);
        event.getPayload().put("requestId", context.getRequestId());
        if (Boolean.TRUE.equals(context.getMetadata().get("resume"))) {
            event.getPayload().put("resume", true);
            event.getPayload().put("resumeToken", context.getMetadata().get("resumeToken"));
        }
        return event;
    }

    /**
     * 生成完成事件。
     *
     * @param context 本轮运行上下文
     * @param text 最终文本
     * @param message 最终结构化消息
     * @param knowledgeReferences 本轮知识候选
     * @return 完成事件
     */
    private AgentRuntimeEvent completed(AgentRuntimeExecutionContext context,
                                        String text,
                                        AgentMessage message,
                                        Map<String, AgentKnowledgeReference> knowledgeReferences) {
        String finalText = text == null ? "" : text;
        AgentMessage finalMessage = message == null ? AgentMessage.text(AgentMessageRole.ASSISTANT, finalText) : message;
        List<AgentKnowledgeReference> matchedReferences = citationMatcher.match(finalText, knowledgeReferences.values());
        finalMessage.setKnowledgeReferences(matchedReferences);
        AgentRuntimeEvent event = base(context, AgentRuntimeEventType.COMPLETED);
        event.getPayload().put("text", finalText);
        event.setMessage(finalMessage);
        return event;
    }

    /**
     * 生成失败事件。
     *
     * @param context 本轮运行上下文
     * @param error 运行异常
     * @return 失败事件
     */
    private AgentRuntimeEvent failed(AgentRuntimeExecutionContext context, Throwable error) {
        AgentRuntimeEvent event = base(context, AgentRuntimeEventType.FAILED);
        event.getPayload().put("message", error == null ? "Agent runtime failed." : error.getMessage());
        event.getPayload().put("errorType", error == null ? null : error.getClass().getName());
        return event;
    }

    /**
     * 生成取消事件。
     *
     * @param context 本轮运行上下文
     * @return 取消事件
     */
    private AgentRuntimeEvent cancelled(AgentRuntimeExecutionContext context) {
        AgentRuntimeEvent event = base(context, AgentRuntimeEventType.CANCELLED);
        event.getPayload().put("reason", context.getCancelReason());
        if (Boolean.TRUE.equals(context.getMetadata().get("resume"))) {
            event.getPayload().put("resume", true);
            event.getPayload().put("resumeToken", context.getMetadata().get("resumeToken"));
        }
        return event;
    }

    /**
     * 将异常转换为运行时事件流。
     *
     * @param context 本轮运行上下文
     * @param error 异常
     * @return 错误收口事件流
     */
    private Flux<AgentRuntimeEvent> errorEventFlux(AgentRuntimeExecutionContext context, Throwable error) {
        if (error instanceof AgentToolApprovalRejectedException approvalRejectedException) {
            context.setCancelReason(approvalRejectedException.getRejectReason());
            saveSession();
            return Flux.just(cancelled(context));
        }
        saveSession();
        return Flux.just(failed(context, error));
    }

    /**
     * 保存 AgentScope 会话状态。
     */
    private void saveSession() {
        agent.saveTo(session, sessionKey);
    }

    /**
     * 响应 Reactor 取消信号。
     *
     * <p>取消本身是调用方终止订阅行为，因此当前订阅者通常无法再收到 {@code CANCELLED}
     * 事件。这里的职责是中断 AgentScope 主线路、释放等待中的审批请求，并完成旁路 sink。</p>
     *
     * @param context 本轮运行上下文
     * @param sideEvents 旁路事件 sink
     * @param finalText 当前已累计的助手文本
     * @param finalMessage 当前已捕获的结构化助手消息
     * @param cancelled 取消去重标记
     */
    private void cancelInternal(AgentRuntimeExecutionContext context,
                                Sinks.Many<AgentRuntimeEvent> sideEvents,
                                StringBuilder finalText,
                                AtomicReference<AgentMessage> finalMessage,
                                AtomicBoolean cancelled) {
        if (!cancelled.compareAndSet(false, true)) {
            return;
        }
        context.setCancelReason("cancelled");
        try {
            approvalCoordinator.cancelAll(context.getCancelReason());
            agent.interrupt();
            persistPartialAssistantOnCancel(finalText, finalMessage);
        } finally {
            sideEvents.tryEmitComplete();
        }
    }

    /**
     * 将取消前已输出的助手内容补写入 AgentScope memory 并保存 session。
     *
     * <p>AgentScope 的正常完成路径会自行把最终助手消息写入 memory。取消订阅时不会触发
     * 完成路径，因此这里仅在已有非空助手内容时补写一次，并始终保存已经进入 memory 的
     * 用户消息，确保下一轮对话能拿到中断前上下文。</p>
     *
     * @param finalText 当前已累计的助手文本
     * @param finalMessage 当前已捕获的结构化助手消息
     */
    private void persistPartialAssistantOnCancel(StringBuilder finalText,
                                                 AtomicReference<AgentMessage> finalMessage) {
        AgentMessage partialMessage = partialAssistantMessage(finalText, finalMessage);
        if (partialMessage != null) {
            agent.getMemory().addMessage(messageAdapter.toMsg(partialMessage));
        }
        saveSession();
    }

    /**
     * 生成取消时可写入 memory 的助手消息。
     *
     * @param finalText 当前已累计的助手文本
     * @param finalMessage 当前已捕获的结构化助手消息
     * @return 非空助手消息，不存在有效内容时返回 null
     */
    private AgentMessage partialAssistantMessage(StringBuilder finalText,
                                                 AtomicReference<AgentMessage> finalMessage) {
        AgentMessage message = finalMessage == null ? null : finalMessage.get();
        if (hasContent(message)) {
            message.setRole(AgentMessageRole.ASSISTANT);
            return message;
        }
        String text = finalText == null ? "" : finalText.toString();
        if (text.isBlank()) {
            return null;
        }
        return AgentMessage.text(AgentMessageRole.ASSISTANT, text);
    }

    /**
     * 判断消息是否有可用于上下文的内容块。
     *
     * @param message 消息
     * @return 存在非空内容块时为 true
     */
    private boolean hasContent(AgentMessage message) {
        if (message == null || message.getContentBlocks() == null || message.getContentBlocks().isEmpty()) {
            return false;
        }
        for (AgentContentBlock block : message.getContentBlocks()) {
            if (block instanceof AgentTextBlock textBlock) {
                if (textBlock.getText() != null && !textBlock.getText().isBlank()) {
                    return true;
                }
                continue;
            }
            if (block != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * 清理一次 stream/resume 片段状态。
     *
     * @param preserveReusableApprovalScopes 是否因 HITL 挂起而保留当前 Turn 的 MCP 批准
     */
    private void cleanupStreamSegment(boolean preserveReusableApprovalScopes) {
        approvalCoordinator.clearExecutionAuthorizations();
        if (!preserveReusableApprovalScopes) {
            approvalCoordinator.clearReusableApprovalScopes();
        }
        turnContextHolder.clear();
        running.set(false);
    }

    /**
     * 从 AgentScope 最终结果中捕获后端收口需要的完整消息。
     *
     * <p>这是后端内部状态收集</p>
     *
     * @param finalText 最终文本
     * @param finalMessage 最终结构化消息引用
     * @param event AgentScope 主线路事件
     */
    private void captureFinalAgentResult(StringBuilder finalText,
                                         AtomicReference<AgentMessage> finalMessage,
                                         Event event) {
        if (event == null
                || event.getType() != EventType.AGENT_RESULT
                || event.getMessage() == null
                || event.getMessage().getGenerateReason() == GenerateReason.REASONING_STOP_REQUESTED) {
            return;
        }
        AgentMessage message = messageAdapter.toAgentMessage(event.getMessage());
        finalMessage.set(message);
        String messageText = textContent(message);
        if (!messageText.isBlank()) {
            finalText.setLength(0);
            finalText.append(messageText);
        }
    }

    /**
     * 从消息增量事件中收集最终助手文本。
     *
     * @param finalText 最终文本
     * @param event 运行时事件
     */
    private void updateFinalText(StringBuilder finalText, AgentRuntimeEvent event) {
        if (event.getEventType() != AgentRuntimeEventType.MESSAGE_DELTA) {
            return;
        }
        Object text = event.getPayload().get("text");
        if (text instanceof String textValue && !textValue.isEmpty()) {
            finalText.append(textValue);
        }
        if (event.getMessage() != null && Boolean.TRUE.equals(event.getPayload().get("last"))) {
            String messageText = textContent(event.getMessage());
            if (!messageText.isBlank()) {
                finalText.setLength(0);
                finalText.append(messageText);
            }
        }
    }

    /**
     * 从最终增量中收集结构化助手消息。
     *
     * @param finalMessage 最终消息引用
     * @param event 运行时事件
     */
    private void updateFinalMessage(AtomicReference<AgentMessage> finalMessage, AgentRuntimeEvent event) {
        if (event.getEventType() == AgentRuntimeEventType.MESSAGE_DELTA
                && Boolean.TRUE.equals(event.getPayload().get("last"))
                && event.getMessage() != null) {
            finalMessage.set(event.getMessage());
        }
    }

    /**
     * 记录本轮是否已被 HITL 暂停。
     *
     * @param suspendedEvent 暂停事件引用
     * @param event 运行时事件
     */
    private void updateSuspendedEvent(AtomicReference<AgentRuntimeEvent> suspendedEvent, AgentRuntimeEvent event) {
        if (event.getEventType() == AgentRuntimeEventType.SUSPENDED) {
            suspendedEvent.set(event);
        }
    }

    /**
     * 从知识库旁路事件中收集本轮候选引用。
     *
     * <p>知识库检索仍属于 AgentScope RAG 主线路；这里仅读取旁路事件 payload，
     * 为最终消息展示引用做保守匹配，不影响 AgentScope memory/session。</p>
     *
     * @param knowledgeReferences 候选引用集合
     * @param event 运行时事件
     */
    @SuppressWarnings("unchecked")
    private void updateKnowledgeReferences(Map<String, AgentKnowledgeReference> knowledgeReferences,
                                           AgentRuntimeEvent event) {
        if (event == null || event.getEventType() != AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL) {
            return;
        }
        Object documentsObject = event.getPayload().get("documents");
        if (!(documentsObject instanceof List<?> documents) || documents.isEmpty()) {
            return;
        }
        Object knowledgeId = event.getPayload().get("knowledgeId");
        Object knowledgeName = event.getPayload().get("knowledgeName");
        Object knowledgeType = event.getPayload().get("knowledgeType");
        Object faqCollection = event.getPayload().get("faqCollection");
        for (Object documentObject : documents) {
            if (!(documentObject instanceof Map<?, ?> documentMap)) {
                continue;
            }
            AgentKnowledgeReference reference = new AgentKnowledgeReference();
            reference.setKnowledgeId(stringValue(knowledgeId));
            reference.setKnowledgeName(stringValue(knowledgeName));
            reference.setDocumentId(stringValue(documentMap.get("documentId")));
            reference.setDocumentName(stringValue(documentMap.get("documentName")));
            reference.setChunkId(stringValue(documentMap.get("chunkId")));
            reference.setChunkContent(stringValue(documentMap.get("chunkContent")));
            reference.setSourceUri(stringValue(documentMap.get("sourceUri")));
            reference.setScore(documentMap.get("score") instanceof Number score ? score.doubleValue() : null);
            Object metadata = documentMap.get("metadata");
            Map<String, Object> referenceMetadata = metadata instanceof Map<?, ?> map
                    ? new LinkedHashMap<>((Map<String, Object>) map)
                    : new LinkedHashMap<>();
            if (knowledgeType != null) {
                referenceMetadata.putIfAbsent("knowledgeType", knowledgeType);
            }
            if (faqCollection != null) {
                referenceMetadata.putIfAbsent("faqCollection", faqCollection);
            }
            reference.setMetadata(referenceMetadata);
            knowledgeReferences.putIfAbsent(knowledgeReferenceKey(reference), reference);
        }
    }

    /**
     * 构建候选知识引用去重键。
     *
     * @param reference 知识引用
     * @return 去重键
     */
    private String knowledgeReferenceKey(AgentKnowledgeReference reference) {
        return String.join(":",
                stringValue(reference.getKnowledgeId()),
                stringValue(reference.getDocumentId()),
                stringValue(reference.getChunkId()));
    }

    /**
     * 提取结构化消息中的文本正文。
     *
     * @param message 结构化消息
     * @return 文本正文
     */
    private String textContent(AgentMessage message) {
        if (message == null || message.getContentBlocks() == null) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (AgentContentBlock block : message.getContentBlocks()) {
            if (block instanceof AgentTextBlock textBlock && textBlock.getText() != null) {
                builder.append(textBlock.getText());
            }
        }
        return builder.toString();
    }

    /**
     * 构建携带公共身份信息的运行时事件。
     *
     * @param context 本轮运行上下文
     * @param type 事件类型
     * @return 运行时事件
     */
    private AgentRuntimeEvent base(AgentRuntimeExecutionContext context, AgentRuntimeEventType type) {
        AgentRuntimeEvent event = AgentRuntimeEvent.of(type);
        event.setTraceId(context.getTraceId());
        event.setSessionId(context.getSessionId());
        event.setAgentId(context.getAgentDefinition().getAgentId());
        event.getMetadata().put("requestId", context.getRequestId());
        event.getMetadata().putAll(nullToEmpty(context.getMetadata()));
        return event;
    }

    /**
     * 空安全 Map。
     *
     * @param map 原 Map
     * @return 非空 Map
     */
    private Map<String, Object> nullToEmpty(Map<String, Object> map) {
        return map == null ? new LinkedHashMap<>() : map;
    }

    /**
     * 将对象转换为字符串。
     *
     * @param value 值
     * @return 字符串
     */
    private String stringValue(Object value) {
        return value == null ? null : String.valueOf(value);
    }

    /**
     * 将 AgentScope 可能输出的累计快照归一化为增量。
     *
     * <p>当前运行时显式使用 {@code incremental(true)}。普通事件携带新增文本，必须原样保留；
     * {@code last=true} 的终态事件才可能携带完整快照，此时只发送尚未输出的尾部。</p>
     */
    private static final class StreamDeltaNormalizer {

        private final Map<String, StringBuilder> emittedValues = new LinkedHashMap<>();

        /**
         * 归一化流式事件。
         *
         * @param event 原事件
         * @return 归一化后的事件
         */
        private AgentRuntimeEvent normalize(AgentRuntimeEvent event) {
            if (event == null || event.getEventType() == null) {
                return event;
            }
            if (event.getEventType() == AgentRuntimeEventType.REASONING_DELTA) {
                normalizePayloadText(event, "reasoning");
            }
            if (event.getEventType() == AgentRuntimeEventType.MESSAGE_DELTA) {
                normalizePayloadText(event, "text");
            }
            return event;
        }

        private void normalizePayloadText(AgentRuntimeEvent event, String payloadKey) {
            Object currentValue = event.getPayload().get(payloadKey);
            if (!(currentValue instanceof String currentText) || currentText.isEmpty()) {
                return;
            }
            String key = streamKey(event, payloadKey);
            boolean last = Boolean.TRUE.equals(event.getPayload().get("last"));
            if (!last) {
                emittedValues.computeIfAbsent(key, ignored -> new StringBuilder()).append(currentText);
                return;
            }
            StringBuilder emitted = emittedValues.remove(key);
            if (emitted != null && currentText.startsWith(emitted.toString())) {
                event.getPayload().put(payloadKey, currentText.substring(emitted.length()));
            }
        }

        private String streamKey(AgentRuntimeEvent event, String payloadKey) {
            Object blockIndex = event.getMetadata().get("blockIndex");
            return String.join(":",
                    event.getMessageId() == null || event.getMessageId().isBlank() ? "default-message" : event.getMessageId(),
                    event.getEventType().name(),
                    payloadKey,
                    blockIndex == null ? "0" : String.valueOf(blockIndex));
        }
    }

    /**
     * 校验初始化请求的必要字段。
     *
     * @param request 初始化请求
     */
    private void validateInitRequest(AgentInitRequest request) {
        if (request == null) {
            throw new AgentRuntimeException("Agent runtime init request is required.");
        }
        if (request.getSessionId() == null || request.getSessionId().isBlank()) {
            throw new AgentRuntimeException("Agent runtime session id is required.");
        }
        if (request.getSessionStore() == null || request.getSessionStore() == NoopAgentSessionStore.INSTANCE) {
            throw new AgentRuntimeException("Agent runtime session store is required.");
        }
        AgentDefinition definition = request.getAgentDefinition();
        if (definition == null) {
            throw new AgentRuntimeException("Agent definition is required.");
        }
        if (definition.getModelSpec() == null) {
            throw new AgentRuntimeException("Agent model spec is required.");
        }
        validateOperateToolConflicts(definition);
    }

    private void validateOperateToolConflicts(AgentDefinition definition) {
        Set<String> operateToolNames = operateToolAdapter.enabledToolNames(definition.getOperateToolSpecs());
        if (operateToolNames.isEmpty()) {
            return;
        }
        for (AgentToolSpec toolSpec : definition.getToolSpecs()) {
            if (toolSpec != null && operateToolNames.contains(toolSpec.getName())) {
                throw new AgentRuntimeException("Agent operate tool conflicts with existing tool: " + toolSpec.getName());
            }
        }
    }

    /**
     * 从初始化请求创建运行时级上下文。
     *
     * @param request 初始化请求
     * @return 运行时级上下文
     */
    private AgentRuntimeExecutionContext createRuntimeContext(AgentInitRequest request) {
        AgentRuntimeExecutionContext context = new AgentRuntimeExecutionContext();
        context.setSessionId(request.getSessionId());
        context.setTraceId(request.getRuntimeContext().getTraceId());
        context.setAgentDefinition(request.getAgentDefinition());
        context.setRuntimeContext(request.getRuntimeContext());
        context.setToolInvokers(request.getToolInvokers());
        context.setKnowledgeRegistrations(request.getKnowledgeRegistrations());
        context.setMemorySnapshot(request.getMemorySnapshot());
        context.setSessionStore(request.getSessionStore());
        context.setConversationRecorder(request.getConversationRecorder());
        context.setMetadata(request.getMetadata());
        return context;
    }

    /**
     * 构建并配置 AgentScope ReActAgent。
     *
     * @param context 运行时级上下文
     * @return AgentScope ReActAgent
     */
    private ReActAgent buildAgent(AgentRuntimeExecutionContext context) {
        AgentDefinition definition = context.getAgentDefinition();
        Model model = modelFactory.create(definition.getModelSpec(), definition.getGenerationOptions());
        Toolkit toolkit = new Toolkit();
        AgentScopeToolkitBuildResult toolkitBuildResult = buildToolkit(context, toolkit);
        Map<String, List<AgentTool>> skillTools = toolkitBuildResult.skillTools();
        AgentScopeMemoryBuildResult memoryResult = memoryAdapter.createMemoryResult(
                context.getMemorySnapshot(), definition.getMemoryPolicy(), model);
        Memory memory = memoryResult.getMemory();
        SkillBox skillBox = skillAdapter.createSkillBox(definition.getSkillBoxSpec(), toolkit, skillTools,
                toolkitBuildResult.skillMcpRegistrations());
        // AutoContextInterceptor 是官方 AutoContextHook 的替代实现。这里仍只注册统一 runtime hook，
        // 避免官方 hook 与 Easy-Agents interceptor 同时触发压缩和 inputMessages 改写。
        AgentRuntimeEventBridge eventBridge = new AgentRuntimeEventBridge(context, turnContextHolder);
        List<AgentRuntimeInterceptor> interceptors = new ArrayList<>();
        if (memory instanceof AutoContextMemory) {
            interceptors.add(new AutoContextInterceptor(eventBridge, memoryResult.getAutoContextConfig()));
        }
        interceptors.add(new MediaReferenceInterceptor(initRequest.getMediaResolver()));
        List<AgentToolSpec> runtimeToolSpecs = mergeToolSpecs(
                definition.getToolSpecs(),
                toolkitBuildResult.knowledgeToolSpecs(),
                toolkitBuildResult.mcpToolSpecs(),
                toolkitBuildResult.operateToolSpecs());
        interceptors.add(new ToolHitlInterceptor(eventBridge, approvalCoordinator,
                runtimeToolSpecs));
        // 注册旁路事件监听器与主线路干预器。观察器只发旁路事件，不修改 AgentScope HookEvent。
        List<AgentRuntimeObserver> observers = new ArrayList<>();
        observers.add(new SkillExecutionObserver(eventBridge, skillContext, skillBox));
        observers.add(new ToolExecutionObserver(eventBridge, skillContext, runtimeToolSpecs));
        observers.add(new ReasoningLifecycleObserver(eventBridge));
        observers.add(new AgentRuntimeErrorObserver(eventBridge));
        AgentRuntimeObservationManager observationManager =
                new AgentRuntimeObservationManager(interceptors, observers);
        ReActAgent.Builder builder = ReActAgent.builder()
                .name(definition.getAgentName())
                .description(definition.getDescription())
                .sysPrompt(SystemPromptComposer.compose(definition))
                .model(model)
                .toolkit(toolkit)
                .memory(memory)
                .maxIters(definition.getExecutionOptions().getMaxIters())
                .generateOptions(modelFactory.toGenerateOptions(definition.getModelSpec(), definition.getGenerationOptions()))
                .hook(new AgentScopeRuntimeHook(observationManager))
                .enablePendingToolRecovery(true)
                .statePersistence(AgentScopeSessionAdapter.toStatePersistence(definition.getPersistencePolicy()));
        if (skillBox != null) {
            builder.skillBox(skillBox);
        }
        return builder.build();
    }

    /**
     * 构建 AgentScope Toolkit，并返回按 Skill ID 分组的工具。
     *
     * @param context 运行时级上下文
     * @param toolkit AgentScope Toolkit
     * @return 按 Skill ID 分组的工具
     */
    private AgentScopeToolkitBuildResult buildToolkit(AgentRuntimeExecutionContext context,
                                                      Toolkit toolkit) {
        Map<String, List<AgentTool>> skillTools = new LinkedHashMap<>();
        if (!context.getAgentDefinition().getExecutionOptions().isToolCallingEnabled()) {
            return new AgentScopeToolkitBuildResult(skillTools, List.of(), List.of(), List.of(), List.of());
        }
        List<AgentToolSpec> knowledgeToolSpecs = knowledgeAdapter.createToolSpecs(context);
        validateRuntimeToolConflicts(context.getAgentDefinition().getToolSpecs(), knowledgeToolSpecs,
                List.of(), List.of());
        for (AgentToolSpec toolSpec : context.getAgentDefinition().getToolSpecs()) {
            AgentToolInvoker invoker = context.getToolInvokers().get(toolSpec.getName());
            AgentSkillBinding skillBinding = skillContext.getToolBinding(toolSpec.getName());
            AgentTool agentTool = toolAdapter.adapt(toolSpec, invoker, context, approvalCoordinator, turnContextHolder,
                    skillContext, skillBinding, false, false, false);
            if (skillBinding == null) {
                toolkit.registerAgentTool(agentTool);
            } else {
                skillTools.computeIfAbsent(skillBinding.getSkillId(), key -> new ArrayList<>()).add(agentTool);
            }
        }
        knowledgeAdapter.registerTools(context, knowledgeToolSpecs, toolkit, toolAdapter,
                approvalCoordinator, turnContextHolder);
        McpRegistration mcpRegistration = mcpToolkitAdapter.register(
                context.getAgentDefinition().getMcpSpecs(), toolkit);
        mcpClients.addAll(mcpRegistration.getClients());
        List<AgentToolSpec> operateToolSpecs = operateToolAdapter.register(
                context.getAgentDefinition().getOperateToolSpecs(), toolkit);
        McpSpecValidator.validateToolConflicts(context.getAgentDefinition().getToolSpecs(),
                mcpRegistration.getToolSpecs(), context.getAgentDefinition().getOperateToolSpecs());
        validateRuntimeToolConflicts(context.getAgentDefinition().getToolSpecs(), knowledgeToolSpecs,
                mcpRegistration.getToolSpecs(), operateToolSpecs);
        return new AgentScopeToolkitBuildResult(skillTools, knowledgeToolSpecs,
                mcpRegistration.getToolSpecs(), operateToolSpecs, mcpRegistration.getSkillRegistrations());
    }

    /**
     * 合并所有运行时工具声明供统一治理与事件展示使用。
     *
     * @param toolSpecs 普通工具声明
     * @param knowledgeToolSpecs 知识库工具声明
     * @param mcpToolSpecs MCP 工具声明
     * @param operateToolSpecs 操作工具声明
     * @return 保持注册顺序的工具声明列表
     */
    private List<AgentToolSpec> mergeToolSpecs(List<AgentToolSpec> toolSpecs,
                                               List<AgentToolSpec> knowledgeToolSpecs,
                                               List<AgentToolSpec> mcpToolSpecs,
                                               List<AgentToolSpec> operateToolSpecs) {
        List<AgentToolSpec> merged = new ArrayList<>();
        if (toolSpecs != null) {
            merged.addAll(toolSpecs);
        }
        if (knowledgeToolSpecs != null) {
            merged.addAll(knowledgeToolSpecs);
        }
        if (mcpToolSpecs != null) {
            merged.addAll(mcpToolSpecs);
        }
        if (operateToolSpecs != null) {
            merged.addAll(operateToolSpecs);
        }
        return merged;
    }

    /**
     * 校验不同来源的运行时工具名称没有冲突。
     *
     * @param toolSpecs 普通工具声明
     * @param knowledgeToolSpecs 知识库工具声明
     * @param mcpToolSpecs MCP 工具声明
     * @param operateToolSpecs 操作工具声明
     * @throws AgentRuntimeException 工具名称重复时抛出
     */
    private void validateRuntimeToolConflicts(List<AgentToolSpec> toolSpecs,
                                              List<AgentToolSpec> knowledgeToolSpecs,
                                              List<AgentToolSpec> mcpToolSpecs,
                                              List<AgentToolSpec> operateToolSpecs) {
        Set<String> names = new LinkedHashSet<>();
        for (List<AgentToolSpec> specs : List.of(
                safeToolSpecs(toolSpecs),
                safeToolSpecs(knowledgeToolSpecs),
                safeToolSpecs(mcpToolSpecs),
                safeToolSpecs(operateToolSpecs))) {
            for (AgentToolSpec spec : specs) {
                if (spec == null || spec.getName() == null || spec.getName().isBlank()) {
                    continue;
                }
                if (!names.add(spec.getName())) {
                    throw new AgentRuntimeException("Agent runtime tool name conflict: " + spec.getName());
                }
            }
        }
    }

    /**
     * 将可空工具列表转换为空安全列表。
     *
     * @param toolSpecs 工具声明
     * @return 非空工具声明列表
     */
    private List<AgentToolSpec> safeToolSpecs(List<AgentToolSpec> toolSpecs) {
        return toolSpecs == null ? List.of() : toolSpecs;
    }

    private void closeMcpClients() {
        for (McpClientWrapper client : mcpClients) {
            if (client == null) {
                continue;
            }
            try {
                client.close();
            } catch (Exception ignored) {
            }
        }
        mcpClients.clear();
    }

    public AgentInitRequest getInitRequest() {
        return initRequest;
    }

    /**
     * 获取当前已构建的 AgentScope ReActAgent。
     *
     * @return AgentScope ReActAgent
     */
    ReActAgent getAgent() {
        return agent;
    }

    private record AgentScopeToolkitBuildResult(Map<String, List<AgentTool>> skillTools,
                                                 List<AgentToolSpec> knowledgeToolSpecs,
                                                 List<AgentToolSpec> mcpToolSpecs,
                                                 List<AgentToolSpec> operateToolSpecs,
                                                 List<McpSkillRegistration> skillMcpRegistrations) {
    }
}
