package vip.mate.tool.builtin; import org.springframework.ai.chat.model.ToolContext; import org.springframework.lang.Nullable; import vip.mate.agent.context.ChatOrigin; /** * 工具执行上下文 — 通过 ThreadLocal 向 @Tool 方法传递执行环境信息 *

* 在 ToolExecutionExecutor.executeSingleTool() 中 set,在 finally 中 clear。 * 视频生成等需要知道 conversationId 的工具从此处获取。 * *

RFC-063r §2.5 兼容期:执行器会同时填充本 ThreadLocal 和 Spring AI 的 * {@link ToolContext}(携带 {@link ChatOrigin})。优先读 ToolContext 的工具 * 调用 {@link #conversationId(ToolContext)} / {@link #username(ToolContext)} * / {@link #workspaceBasePath(ToolContext)} 等三参重载即可——传入 ctx 不为 * null 时优先返回 origin 的字段,否则回退到 ThreadLocal。 * * @author MateClaw Team */ public final class ToolExecutionContext { private static final ThreadLocal CONVERSATION_ID = new ThreadLocal<>(); private static final ThreadLocal USERNAME = new ThreadLocal<>(); /** 工作区活动目录(为空不限制) */ private static final ThreadLocal WORKSPACE_BASE_PATH = new ThreadLocal<>(); private ToolExecutionContext() {} public static void set(String conversationId, String username) { CONVERSATION_ID.set(conversationId); USERNAME.set(username); WORKSPACE_BASE_PATH.remove(); } public static void set(String conversationId, String username, String workspaceBasePath) { CONVERSATION_ID.set(conversationId); USERNAME.set(username); WORKSPACE_BASE_PATH.set(workspaceBasePath); } public static String conversationId() { return CONVERSATION_ID.get(); } public static String username() { return USERNAME.get(); } /** 获取当前工作区活动目录,为 null 表示不限制 */ public static String workspaceBasePath() { return WORKSPACE_BASE_PATH.get(); } public static void clear() { CONVERSATION_ID.remove(); USERNAME.remove(); WORKSPACE_BASE_PATH.remove(); } // ===== RFC-063r §2.5: ToolContext-aware accessors ===== // // Preferred over the parameter-less variants: read from the explicit // Spring AI ToolContext (carries ChatOrigin) when available, otherwise // fall back to the legacy ThreadLocal so legacy paths keep working. public static String conversationId(@Nullable ToolContext ctx) { if (ctx != null) { String v = ChatOrigin.from(ctx).conversationId(); if (v != null && !v.isEmpty()) return v; } return CONVERSATION_ID.get(); } public static String username(@Nullable ToolContext ctx) { if (ctx != null) { String v = ChatOrigin.from(ctx).requesterId(); if (v != null && !v.isEmpty()) return v; } return USERNAME.get(); } public static String workspaceBasePath(@Nullable ToolContext ctx) { if (ctx != null) { String v = ChatOrigin.from(ctx).workspaceBasePath(); if (v != null && !v.isBlank()) return v; } return WORKSPACE_BASE_PATH.get(); } public static Long originMessageId(@Nullable ToolContext ctx) { return ctx == null ? null : ChatOrigin.from(ctx).originMessageId(); } }