From 9794b39572eae7ad8b7d999e2705cb438c1d8c67 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Thu, 12 Mar 2026 15:32:28 +0800 Subject: [PATCH] refactor(runner): adjust the directory organization of runner --- .../core/action/runner/LocalRunnerClient.java | 10 +- .../action/runner/RunnerExecutionPolicy.java | 4 - .../CommandExecutionService.java | 4 +- .../LocalProcessCommandExecutionService.java | 4 +- .../{ => execution}/McpActionExecutor.java | 10 +- .../OriginExecutionService.java | 9 +- .../{ => mcp}/DynamicActionMcpManager.java | 18 +- .../runner/{ => mcp}/McpClientRegistry.java | 16 +- .../runner/{ => mcp}/McpConfigWatcher.java | 20 ++- .../runner/{ => mcp}/McpDescWatcher.java | 9 +- .../runner/{ => mcp}/McpMetaRegistry.java | 22 +-- .../runner/{ => mcp}/McpTransportConfig.java | 4 +- .../runner/{ => mcp}/McpTransportFactory.java | 7 +- .../runner/policy/RunnerExecutionPolicy.java | 4 + .../{ => support}/ActionSerializer.java | 14 +- .../{ => support}/DirectoryWatchSupport.java | 32 ++-- .../runner/RunnerStabilizationTest.java | 160 ------------------ 17 files changed, 102 insertions(+), 245 deletions(-) delete mode 100644 Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/RunnerExecutionPolicy.java rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => execution}/CommandExecutionService.java (76%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => execution}/LocalProcessCommandExecutionService.java (94%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => execution}/McpActionExecutor.java (86%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => execution}/OriginExecutionService.java (79%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/DynamicActionMcpManager.java (94%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpClientRegistry.java (67%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpConfigWatcher.java (94%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpDescWatcher.java (82%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpMetaRegistry.java (93%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpTransportConfig.java (74%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => mcp}/McpTransportFactory.java (87%) create mode 100644 Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/policy/RunnerExecutionPolicy.java rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => support}/ActionSerializer.java (88%) rename Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/{ => support}/DirectoryWatchSupport.java (84%) delete mode 100644 Partner-Core/src/test/java/work/slhaf/partner/core/action/runner/RunnerStabilizationTest.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalRunnerClient.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalRunnerClient.java index c7375c75..75f4e8be 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalRunnerClient.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalRunnerClient.java @@ -9,6 +9,12 @@ import work.slhaf.partner.core.action.entity.ActionFileMetaData; import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.exception.ActionInitFailedException; +import work.slhaf.partner.core.action.runner.execution.CommandExecutionService; +import work.slhaf.partner.core.action.runner.execution.LocalProcessCommandExecutionService; +import work.slhaf.partner.core.action.runner.execution.McpActionExecutor; +import work.slhaf.partner.core.action.runner.execution.OriginExecutionService; +import work.slhaf.partner.core.action.runner.mcp.*; +import work.slhaf.partner.core.action.runner.support.ActionSerializer; import java.io.IOException; import java.nio.file.Path; @@ -22,8 +28,8 @@ import static work.slhaf.partner.common.util.PathUtil.buildPathStr; @Slf4j public class LocalRunnerClient extends RunnerClient implements AutoCloseable { - static final String MCP_NAME_DESC = "mcp-desc"; - static final String MCP_NAME_DYNAMIC = "mcp-dynamic"; + public static final String MCP_NAME_DESC = "mcp-desc"; + public static final String MCP_NAME_DYNAMIC = "mcp-dynamic"; private final String tmpActionPath; private final String dynamicActionPath; diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/RunnerExecutionPolicy.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/RunnerExecutionPolicy.java deleted file mode 100644 index 4a481ea8..00000000 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/RunnerExecutionPolicy.java +++ /dev/null @@ -1,4 +0,0 @@ -package work.slhaf.partner.core.action.runner; - -interface RunnerExecutionPolicy { -} diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/CommandExecutionService.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/CommandExecutionService.java similarity index 76% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/CommandExecutionService.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/CommandExecutionService.java index f920428f..e302741e 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/CommandExecutionService.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/CommandExecutionService.java @@ -1,11 +1,11 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.execution; import lombok.Data; import java.util.List; import java.util.Map; -interface CommandExecutionService { +public interface CommandExecutionService { String[] buildCommands(String ext, Map params, String absolutePath); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalProcessCommandExecutionService.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/LocalProcessCommandExecutionService.java similarity index 94% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalProcessCommandExecutionService.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/LocalProcessCommandExecutionService.java index fa0938b5..35b13a34 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/LocalProcessCommandExecutionService.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/LocalProcessCommandExecutionService.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.execution; import java.io.BufferedReader; import java.io.InputStreamReader; @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -class LocalProcessCommandExecutionService implements CommandExecutionService { +public class LocalProcessCommandExecutionService implements CommandExecutionService { @Override public String[] buildCommands(String ext, Map params, String absolutePath) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpActionExecutor.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/McpActionExecutor.java similarity index 86% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpActionExecutor.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/McpActionExecutor.java index 09e4418b..c4add8d1 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpActionExecutor.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/McpActionExecutor.java @@ -1,21 +1,23 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.execution; import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.spec.McpSchema; import work.slhaf.partner.core.action.entity.MetaAction; +import work.slhaf.partner.core.action.runner.RunnerClient; +import work.slhaf.partner.core.action.runner.mcp.McpClientRegistry; import java.util.List; import java.util.stream.Collectors; -class McpActionExecutor { +public class McpActionExecutor { private final McpClientRegistry mcpClientRegistry; - McpActionExecutor(McpClientRegistry mcpClientRegistry) { + public McpActionExecutor(McpClientRegistry mcpClientRegistry) { this.mcpClientRegistry = mcpClientRegistry; } - RunnerClient.RunnerResponse run(MetaAction metaAction) { + public RunnerClient.RunnerResponse run(MetaAction metaAction) { RunnerClient.RunnerResponse response = new RunnerClient.RunnerResponse(); McpSyncClient mcpClient = mcpClientRegistry.get(metaAction.getLocation()); if (mcpClient == null) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/OriginExecutionService.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/OriginExecutionService.java similarity index 79% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/OriginExecutionService.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/OriginExecutionService.java index f7920629..9b869565 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/OriginExecutionService.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/execution/OriginExecutionService.java @@ -1,19 +1,20 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.execution; import cn.hutool.core.io.FileUtil; import work.slhaf.partner.core.action.entity.MetaAction; +import work.slhaf.partner.core.action.runner.RunnerClient; import java.io.File; -class OriginExecutionService { +public class OriginExecutionService { private final CommandExecutionService commandExecutionService; - OriginExecutionService(CommandExecutionService commandExecutionService) { + public OriginExecutionService(CommandExecutionService commandExecutionService) { this.commandExecutionService = commandExecutionService; } - RunnerClient.RunnerResponse run(MetaAction metaAction) { + public RunnerClient.RunnerResponse run(MetaAction metaAction) { RunnerClient.RunnerResponse response = new RunnerClient.RunnerResponse(); File file = new File(metaAction.getLocation()); String ext = FileUtil.getSuffix(file); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DynamicActionMcpManager.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/DynamicActionMcpManager.java similarity index 94% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DynamicActionMcpManager.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/DynamicActionMcpManager.java index 5bd1aad9..7efa380c 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DynamicActionMcpManager.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/DynamicActionMcpManager.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import cn.hutool.core.io.FileUtil; import cn.hutool.json.JSONUtil; @@ -15,6 +15,8 @@ import reactor.core.scheduler.Schedulers; import work.slhaf.partner.common.mcp.InProcessMcpTransport; import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.exception.ActionInitFailedException; +import work.slhaf.partner.core.action.runner.execution.CommandExecutionService; +import work.slhaf.partner.core.action.runner.support.DirectoryWatchSupport; import java.io.File; import java.io.IOException; @@ -33,7 +35,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @Slf4j -class DynamicActionMcpManager implements AutoCloseable { +public class DynamicActionMcpManager implements AutoCloseable { private final Path root; private final ConcurrentHashMap existedMetaActions; @@ -42,10 +44,10 @@ class DynamicActionMcpManager implements AutoCloseable { private final InProcessMcpTransport clientTransport; private final DirectoryWatchSupport watchSupport; - DynamicActionMcpManager(Path root, - ConcurrentHashMap existedMetaActions, - ExecutorService executor, - CommandExecutionService commandExecutionService) throws IOException { + public DynamicActionMcpManager(Path root, + ConcurrentHashMap existedMetaActions, + ExecutorService executor, + CommandExecutionService commandExecutionService) throws IOException { this.root = root; this.existedMetaActions = existedMetaActions; this.commandExecutionService = commandExecutionService; @@ -65,11 +67,11 @@ class DynamicActionMcpManager implements AutoCloseable { .onOverflow((thisDir, context) -> reconcile()); } - McpTransportConfig.InProcess clientConfig(int timeout) { + public McpTransportConfig.InProcess clientConfig(int timeout) { return new McpTransportConfig.InProcess(timeout, clientTransport); } - void start() { + public void start() { watchSupport.start(); log.info("DynamicActionMcp 文件监听注册完毕"); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpClientRegistry.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpClientRegistry.java similarity index 67% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpClientRegistry.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpClientRegistry.java index 6e38a18a..5ab2d9b5 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpClientRegistry.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpClientRegistry.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import io.modelcontextprotocol.client.McpSyncClient; @@ -6,22 +6,22 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -class McpClientRegistry implements AutoCloseable { +public class McpClientRegistry implements AutoCloseable { private final ConcurrentHashMap clients = new ConcurrentHashMap<>(); - McpSyncClient get(String serverName) { + public McpSyncClient get(String serverName) { return clients.get(serverName); } - void register(String serverName, McpSyncClient client) { + public void register(String serverName, McpSyncClient client) { McpSyncClient old = clients.put(serverName, client); if (old != null && old != client) { old.close(); } } - McpSyncClient remove(String serverName) { + public McpSyncClient remove(String serverName) { McpSyncClient client = detach(serverName); if (client != null) { client.close(); @@ -29,15 +29,15 @@ class McpClientRegistry implements AutoCloseable { return client; } - McpSyncClient detach(String serverName) { + public McpSyncClient detach(String serverName) { return clients.remove(serverName); } - boolean contains(String serverName) { + public boolean contains(String serverName) { return clients.containsKey(serverName); } - Set listIds() { + public Set listIds() { return new HashSet<>(clients.keySet()); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpConfigWatcher.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpConfigWatcher.java similarity index 94% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpConfigWatcher.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpConfigWatcher.java index f6b1d32a..1a446f8a 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpConfigWatcher.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpConfigWatcher.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import cn.hutool.json.JSONUtil; import io.modelcontextprotocol.client.McpClient; @@ -6,6 +6,8 @@ import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.spec.McpSchema; import lombok.extern.slf4j.Slf4j; import work.slhaf.partner.core.action.entity.MetaActionInfo; +import work.slhaf.partner.core.action.runner.LocalRunnerClient; +import work.slhaf.partner.core.action.runner.support.DirectoryWatchSupport; import java.io.File; import java.io.IOException; @@ -20,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; @Slf4j -class McpConfigWatcher implements AutoCloseable { +public class McpConfigWatcher implements AutoCloseable { private final Path root; private final ConcurrentHashMap existedMetaActions; @@ -30,12 +32,12 @@ class McpConfigWatcher implements AutoCloseable { private final DirectoryWatchSupport watchSupport; private final Map mcpConfigFileCache = new HashMap<>(); - McpConfigWatcher(Path root, - ConcurrentHashMap existedMetaActions, - McpClientRegistry mcpClientRegistry, - McpTransportFactory mcpTransportFactory, - McpMetaRegistry mcpMetaRegistry, - ExecutorService executor) throws IOException { + public McpConfigWatcher(Path root, + ConcurrentHashMap existedMetaActions, + McpClientRegistry mcpClientRegistry, + McpTransportFactory mcpTransportFactory, + McpMetaRegistry mcpMetaRegistry, + ExecutorService executor) throws IOException { this.root = root; this.existedMetaActions = existedMetaActions; this.mcpClientRegistry = mcpClientRegistry; @@ -48,7 +50,7 @@ class McpConfigWatcher implements AutoCloseable { .onOverflow((thisDir, context) -> checkAndReload(false)); } - void start() { + public void start() { watchSupport.start(); log.info("CommonMcp 文件监听注册完毕"); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpDescWatcher.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpDescWatcher.java similarity index 82% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpDescWatcher.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpDescWatcher.java index c84e1db1..5e533025 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpDescWatcher.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpDescWatcher.java @@ -1,6 +1,7 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import lombok.extern.slf4j.Slf4j; +import work.slhaf.partner.core.action.runner.support.DirectoryWatchSupport; import java.io.IOException; import java.nio.file.Files; @@ -8,13 +9,13 @@ import java.nio.file.Path; import java.util.concurrent.ExecutorService; @Slf4j -class McpDescWatcher implements AutoCloseable { +public class McpDescWatcher implements AutoCloseable { private final Path root; private final McpMetaRegistry mcpMetaRegistry; private final DirectoryWatchSupport watchSupport; - McpDescWatcher(Path root, McpMetaRegistry mcpMetaRegistry, ExecutorService executor) throws IOException { + public McpDescWatcher(Path root, McpMetaRegistry mcpMetaRegistry, ExecutorService executor) throws IOException { this.root = root; this.mcpMetaRegistry = mcpMetaRegistry; this.watchSupport = new DirectoryWatchSupport(new DirectoryWatchSupport.Context(root), executor, true, () -> mcpMetaRegistry.loadDirectory(root)) @@ -24,7 +25,7 @@ class McpDescWatcher implements AutoCloseable { .onOverflow((thisDir, context) -> mcpMetaRegistry.reconcile(root)); } - void start() { + public void start() { watchSupport.start(); log.info("DescMcp 文件监听注册完毕"); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpMetaRegistry.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpMetaRegistry.java similarity index 93% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpMetaRegistry.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpMetaRegistry.java index 21f31b6a..fda8bf0b 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpMetaRegistry.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpMetaRegistry.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSONObject; @@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; @Slf4j -class McpMetaRegistry implements AutoCloseable { +public class McpMetaRegistry implements AutoCloseable { private final ConcurrentHashMap existedMetaActions; private final ConcurrentHashMap descCache = new ConcurrentHashMap<>(); @@ -33,7 +33,7 @@ class McpMetaRegistry implements AutoCloseable { private final McpStatelessAsyncServer descServer; private final InProcessMcpTransport clientTransport; - McpMetaRegistry(ConcurrentHashMap existedMetaActions) { + public McpMetaRegistry(ConcurrentHashMap existedMetaActions) { this.existedMetaActions = existedMetaActions; InProcessMcpTransport.Pair pair = InProcessMcpTransport.pair(); this.clientTransport = pair.clientSide(); @@ -46,11 +46,11 @@ class McpMetaRegistry implements AutoCloseable { .build(); } - McpTransportConfig.InProcess clientConfig(String serverName, int timeout) { + public McpTransportConfig.InProcess clientConfig(String serverName, int timeout) { return new McpTransportConfig.InProcess(timeout, clientTransport); } - void loadDirectory(Path root) { + public void loadDirectory(Path root) { File[] files = loadFiles(root); if (files == null) { return; @@ -60,11 +60,11 @@ class McpMetaRegistry implements AutoCloseable { } } - boolean addOrUpdate(Path path) { + public boolean addOrUpdate(Path path) { return addOrUpdate(path.toFile()); } - boolean addOrUpdate(File file) { + public boolean addOrUpdate(File file) { String name = file.getName(); if (!isValidDescFile(name)) { return false; @@ -86,7 +86,7 @@ class McpMetaRegistry implements AutoCloseable { } } - void remove(Path path) { + public void remove(Path path) { String uri = path.toUri().toString(); String actionKey = path.getFileName().toString().replace(".desc.json", ""); descCache.remove(uri); @@ -103,7 +103,7 @@ class McpMetaRegistry implements AutoCloseable { } } - void reconcile(Path root) { + public void reconcile(Path root) { File[] files = loadFiles(root); if (files == null) { return; @@ -133,7 +133,7 @@ class McpMetaRegistry implements AutoCloseable { } } - MetaActionInfo buildMetaActionInfo(String serverId, McpSchema.Tool tool) { + public MetaActionInfo buildMetaActionInfo(String serverId, McpSchema.Tool tool) { String actionKey = serverId + "::" + tool.name(); MetaActionInfo baseInfo = buildToolMetaActionInfo(tool); originalInfoCache.put(actionKey, copyMetaActionInfo(baseInfo)); @@ -161,7 +161,7 @@ class McpMetaRegistry implements AutoCloseable { return new McpStatelessServerFeatures.AsyncResourceSpecification(resource, readHandler); } - boolean isValidDescFile(String fileName) { + public boolean isValidDescFile(String fileName) { return fileName.endsWith(".desc.json") && fileName.contains("::"); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportConfig.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportConfig.java similarity index 74% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportConfig.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportConfig.java index 1c8823bc..a8b522a4 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportConfig.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportConfig.java @@ -1,11 +1,11 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import work.slhaf.partner.common.mcp.InProcessMcpTransport; import java.util.List; import java.util.Map; -sealed interface McpTransportConfig permits McpTransportConfig.Http, McpTransportConfig.Stdio, McpTransportConfig.InProcess { +public sealed interface McpTransportConfig permits McpTransportConfig.Http, McpTransportConfig.Stdio, McpTransportConfig.InProcess { int timeout(); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportFactory.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportFactory.java similarity index 87% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportFactory.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportFactory.java index 79a9328d..034999bb 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/McpTransportFactory.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/mcp/McpTransportFactory.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.mcp; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.client.transport.ServerParameters; @@ -7,13 +7,14 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequ import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.spec.McpClientTransport; +import work.slhaf.partner.core.action.runner.policy.RunnerExecutionPolicy; import java.net.URI; import java.net.http.HttpRequest; -class McpTransportFactory { +public class McpTransportFactory { - McpClientTransport create(McpTransportConfig config, RunnerExecutionPolicy policy) { + public McpClientTransport create(McpTransportConfig config, RunnerExecutionPolicy policy) { return switch (config) { case McpTransportConfig.Stdio stdio -> { ServerParameters serverParameters = ServerParameters.builder(stdio.command()) diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/policy/RunnerExecutionPolicy.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/policy/RunnerExecutionPolicy.java new file mode 100644 index 00000000..f2b63ec5 --- /dev/null +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/policy/RunnerExecutionPolicy.java @@ -0,0 +1,4 @@ +package work.slhaf.partner.core.action.runner.policy; + +public interface RunnerExecutionPolicy { +} diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/ActionSerializer.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/ActionSerializer.java similarity index 88% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/ActionSerializer.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/ActionSerializer.java index 03a14129..634e2bd2 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/ActionSerializer.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/ActionSerializer.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.support; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; @@ -17,17 +17,17 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; @Slf4j -class ActionSerializer { +public class ActionSerializer { private final String tmpActionPath; private final String dynamicActionPath; - ActionSerializer(String tmpActionPath, String dynamicActionPath) { + public ActionSerializer(String tmpActionPath, String dynamicActionPath) { this.tmpActionPath = tmpActionPath; this.dynamicActionPath = dynamicActionPath; } - static String normalizeCodeType(String codeType) { + public static String normalizeCodeType(String codeType) { if (codeType == null || codeType.isBlank()) { throw new IllegalArgumentException("codeType 不能为空"); } @@ -48,11 +48,11 @@ class ActionSerializer { } } - String buildTmpPath(String actionKey, String codeType) { + public String buildTmpPath(String actionKey, String codeType) { return Path.of(tmpActionPath, System.currentTimeMillis() + "-" + actionKey + normalizeCodeType(codeType)).toString(); } - void tmpSerialize(MetaAction tempAction, String code, String codeType) throws IOException { + public void tmpSerialize(MetaAction tempAction, String code, String codeType) throws IOException { log.debug("行动程序临时序列化: {}", tempAction); Path path = Path.of(tempAction.getLocation()); validateTmpLocation(path, codeType); @@ -70,7 +70,7 @@ class ActionSerializer { } } - void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) { + public void persistSerialize(MetaActionInfo metaActionInfo, ActionFileMetaData fileMetaData) { log.debug("行动程序持久序列化: {}", metaActionInfo); val baseDir = Path.of(dynamicActionPath); diff --git a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DirectoryWatchSupport.java b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/DirectoryWatchSupport.java similarity index 84% rename from Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DirectoryWatchSupport.java rename to Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/DirectoryWatchSupport.java index 3df04aa9..e6b3c87b 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/DirectoryWatchSupport.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/core/action/runner/support/DirectoryWatchSupport.java @@ -1,4 +1,4 @@ -package work.slhaf.partner.core.action.runner; +package work.slhaf.partner.core.action.runner.support; import lombok.extern.slf4j.Slf4j; @@ -12,45 +12,46 @@ import java.util.stream.Stream; import static java.nio.file.StandardWatchEventKinds.*; @Slf4j -class DirectoryWatchSupport implements Closeable { +public class DirectoryWatchSupport implements Closeable { private final Context ctx; private final Map, EventHandler> handlers = new HashMap<>(); private final ExecutorService executor; private final boolean watchAll; private final InitLoader initLoader; - DirectoryWatchSupport(Context ctx, ExecutorService executor, boolean watchAll, InitLoader initLoader) { + + public DirectoryWatchSupport(Context ctx, ExecutorService executor, boolean watchAll, InitLoader initLoader) { this.ctx = ctx; this.executor = executor; this.watchAll = watchAll; this.initLoader = initLoader; } - DirectoryWatchSupport onCreate(EventHandler handler) { + public DirectoryWatchSupport onCreate(EventHandler handler) { ctx.kinds().add(ENTRY_CREATE); handlers.put(ENTRY_CREATE, handler); return this; } - DirectoryWatchSupport onModify(EventHandler handler) { + public DirectoryWatchSupport onModify(EventHandler handler) { ctx.kinds().add(ENTRY_MODIFY); handlers.put(ENTRY_MODIFY, handler); return this; } - DirectoryWatchSupport onDelete(EventHandler handler) { + public DirectoryWatchSupport onDelete(EventHandler handler) { ctx.kinds().add(ENTRY_DELETE); handlers.put(ENTRY_DELETE, handler); return this; } - DirectoryWatchSupport onOverflow(EventHandler handler) { + public DirectoryWatchSupport onOverflow(EventHandler handler) { ctx.kinds().add(OVERFLOW); handlers.put(OVERFLOW, handler); return this; } - void start() { + public void start() { registerPath(); if (initLoader != null) { initLoader.load(); @@ -58,15 +59,15 @@ class DirectoryWatchSupport implements Closeable { executor.execute(buildWatchTask()); } - Context context() { + public Context context() { return ctx; } - boolean isWatching(Path dir) { + public boolean isWatching(Path dir) { return ctx.watchKeys().values().stream().anyMatch(dir::equals); } - void registerDirectory(Path dir) throws IOException { + public void registerDirectory(Path dir) throws IOException { if (!java.nio.file.Files.isDirectory(dir) || isWatching(dir)) { return; } @@ -148,16 +149,17 @@ class DirectoryWatchSupport implements Closeable { ctx.watchKeys().clear(); } - interface EventHandler { + public interface EventHandler { void handle(Path thisDir, Path context); } - interface InitLoader { + public interface InitLoader { void load(); } - record Context(Path root, WatchService watchService, Map watchKeys, Set> kinds) { - Context(Path root) throws IOException { + public record Context(Path root, WatchService watchService, Map watchKeys, + Set> kinds) { + public Context(Path root) throws IOException { this(root, FileSystems.getDefault().newWatchService(), new HashMap<>(), new LinkedHashSet<>()); } } diff --git a/Partner-Core/src/test/java/work/slhaf/partner/core/action/runner/RunnerStabilizationTest.java b/Partner-Core/src/test/java/work/slhaf/partner/core/action/runner/RunnerStabilizationTest.java deleted file mode 100644 index ce79338d..00000000 --- a/Partner-Core/src/test/java/work/slhaf/partner/core/action/runner/RunnerStabilizationTest.java +++ /dev/null @@ -1,160 +0,0 @@ -package work.slhaf.partner.core.action.runner; - -import io.modelcontextprotocol.client.McpSyncClient; -import io.modelcontextprotocol.json.McpJsonMapper; -import io.modelcontextprotocol.spec.McpSchema; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; -import work.slhaf.partner.core.action.entity.MetaAction; -import work.slhaf.partner.core.action.entity.MetaActionInfo; - -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -class RunnerStabilizationTest { - - @Test - void actionSerializerUsesNormalizedCodeType(@TempDir Path tempDir) throws Exception { - ActionSerializer serializer = new ActionSerializer(tempDir.toString(), tempDir.toString()); - String builtPath = serializer.buildTmpPath("demo", "py"); - Assertions.assertTrue(builtPath.endsWith(".py")); - - MetaAction metaAction = new MetaAction("demo", false, MetaAction.Type.ORIGIN, builtPath); - serializer.tmpSerialize(metaAction, "print('ok')", ".py"); - - Assertions.assertTrue(Files.exists(Path.of(builtPath))); - Assertions.assertEquals("print('ok')", Files.readString(Path.of(builtPath))); - Assertions.assertThrows(Exception.class, () -> serializer.tmpSerialize(metaAction, "print('bad')", ".sh")); - } - - @Test - void mcpTransportConfigHasValueEquality() { - McpTransportConfig.Stdio left = new McpTransportConfig.Stdio(30, "npx", Map.of("A", "1"), List.of("-y", "demo")); - McpTransportConfig.Stdio right = new McpTransportConfig.Stdio(30, "npx", Map.of("A", "1"), List.of("-y", "demo")); - - Assertions.assertEquals(left, right); - Assertions.assertEquals(left.hashCode(), right.hashCode()); - } - - @Test - void mcpConfigWatcherReadParamsAcceptsTimeout(@TempDir Path tempDir) throws Exception { - ConcurrentHashMap existedMetaActions = new ConcurrentHashMap<>(); - ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - McpConfigWatcher watcher = new McpConfigWatcher( - tempDir, - existedMetaActions, - new McpClientRegistry(), - new McpTransportFactory(), - new McpMetaRegistry(existedMetaActions), - executor - ); - try { - Method readParams = McpConfigWatcher.class.getDeclaredMethod("readParams", cn.hutool.json.JSONObject.class); - readParams.setAccessible(true); - - cn.hutool.json.JSONObject stdioJson = cn.hutool.json.JSONUtil.parseObj(""" - { - "command": "npx", - "args": ["-y", "demo"], - "env": {}, - "timeout": 45 - } - """); - Object stdioConfig = readParams.invoke(watcher, stdioJson); - - Assertions.assertInstanceOf(McpTransportConfig.Stdio.class, stdioConfig); - Assertions.assertEquals(45, ((McpTransportConfig.Stdio) stdioConfig).timeout()); - } finally { - watcher.close(); - executor.shutdownNow(); - } - } - - @Test - void localRunnerClientCloseIsIdempotent(@TempDir Path tempDir) { - ConcurrentHashMap existedMetaActions = new ConcurrentHashMap<>(); - ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - LocalRunnerClient client = new LocalRunnerClient(existedMetaActions, executor, tempDir.toString()); - try { - client.close(); - client.close(); - } finally { - executor.shutdownNow(); - } - } - - @Test - void mcpActionExecutorUsesStructuredContentThenTextContent() { - McpClientRegistry registry = new McpClientRegistry(); - McpSyncClient client = Mockito.mock(McpSyncClient.class); - registry.register("demo", client); - - McpActionExecutor executor = new McpActionExecutor(registry); - MetaAction metaAction = new MetaAction("tool", false, MetaAction.Type.MCP, "demo"); - - Mockito.when(client.callTool(Mockito.any())).thenReturn( - new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("hello")), null, null, Map.of()) - ); - RunnerClient.RunnerResponse textResponse = executor.run(metaAction); - Assertions.assertTrue(textResponse.isOk()); - Assertions.assertEquals("hello", textResponse.getData()); - - Mockito.when(client.callTool(Mockito.any())).thenReturn( - new McpSchema.CallToolResult(List.of(), Boolean.FALSE, Map.of("k", "v"), Map.of()) - ); - RunnerClient.RunnerResponse structuredResponse = executor.run(metaAction); - Assertions.assertTrue(structuredResponse.isOk()); - Assertions.assertEquals("{k=v}", structuredResponse.getData()); - } - - @Test - void mcpMetaRegistryFallsBackToOriginalToolMetaAfterDescRemoval(@TempDir Path tempDir) throws Exception { - ConcurrentHashMap existedMetaActions = new ConcurrentHashMap<>(); - McpMetaRegistry registry = new McpMetaRegistry(existedMetaActions); - try { - McpSchema.Tool tool = McpSchema.Tool.builder() - .name("tool") - .description("tool description") - .inputSchema(McpJsonMapper.getDefault(), "{\"type\":\"object\",\"properties\":{}}") - .outputSchema(Map.of("type", "string")) - .meta(Map.of("io", true, "pre", List.of("pre"), "post", List.of("post"), "strict", true, "tag", List.of("tag"))) - .build(); - - MetaActionInfo baseInfo = registry.buildMetaActionInfo("demo", tool); - existedMetaActions.put("demo::tool", baseInfo); - - Path descFile = tempDir.resolve("demo::tool.desc.json"); - Files.writeString(descFile, """ - { - "io": false, - "params": {}, - "description": "desc override", - "tags": ["desc"], - "preActions": [], - "postActions": [], - "strictDependencies": false, - "responseSchema": {} - } - """); - - Assertions.assertTrue(registry.addOrUpdate(descFile)); - Assertions.assertEquals("desc override", existedMetaActions.get("demo::tool").getDescription()); - - registry.remove(descFile); - MetaActionInfo restoredInfo = existedMetaActions.get("demo::tool"); - Assertions.assertEquals("tool description", restoredInfo.getDescription()); - Assertions.assertTrue(restoredInfo.isIo()); - Assertions.assertEquals(List.of("tag"), restoredInfo.getTags()); - } finally { - registry.close(); - } - } -}