diff --git a/java/pom.xml b/java/pom.xml index 65bb80556..ae2af3d68 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -167,21 +167,35 @@ org.apache.maven.plugins maven-clean-plugin 3.4.1 - - - true - + + + + default-clean + + true + false + + + + post-clean-sweep + post-clean + + clean + + + true + + + org.apache.maven.plugins diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index 2d6c9496b..535af044f 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -1433,6 +1433,18 @@ function wrapperResultClassName(method: RpcMethodNode): string { ) { return rpcMethodToClassName(method.rpcMethod) + "Result"; } + + // Free-form object with additionalProperties (e.g., x-opaque-json) → JsonNode + if ( + result && + typeof result === "object" && + result.type === "object" && + result.additionalProperties && + !result.properties + ) { + return "JsonNode"; + } + return "Void"; } @@ -1571,7 +1583,13 @@ async function generateNamespaceApiFile( for (const [key, method] of tree.methods) { const resultClass = wrapperResultClassName(method); const paramsClass = wrapperParamsClassName(method); - if (resultClass !== "Void") allImports.add(`${packageName}.${resultClass}`); + if (resultClass !== "Void") { + if (resultClass === "JsonNode") { + allImports.add("com.fasterxml.jackson.databind.JsonNode"); + } else { + allImports.add(`${packageName}.${resultClass}`); + } + } if (paramsClass) allImports.add(`${packageName}.${paramsClass}`); const { lines, needsMapper: nm } = generateApiMethod(key, method, isSession, sessionIdExpr); @@ -1690,7 +1708,13 @@ async function generateRpcRootFile( for (const [key, method] of tree.methods) { const resultClass = wrapperResultClassName(method); const paramsClass = wrapperParamsClassName(method); - if (resultClass !== "Void") allImports.add(`${packageName}.${resultClass}`); + if (resultClass !== "Void") { + if (resultClass === "JsonNode") { + allImports.add("com.fasterxml.jackson.databind.JsonNode"); + } else { + allImports.add(`${packageName}.${resultClass}`); + } + } if (paramsClass) allImports.add(`${packageName}.${paramsClass}`); const { lines, needsMapper: nm } = generateApiMethod(key, method, isSession, sessionIdExpr); diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java index b6c131a6a..48f4ed2c7 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java +++ b/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java @@ -7,6 +7,7 @@ package com.github.copilot.generated.rpc; +import com.fasterxml.jackson.databind.JsonNode; import java.util.concurrent.CompletableFuture; import javax.annotation.processing.Generated; @@ -68,10 +69,10 @@ public CompletableFuture listTools(SessionMcpApps * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ - public CompletableFuture callTool(SessionMcpAppsCallToolParams params) { + public CompletableFuture callTool(SessionMcpAppsCallToolParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.mcp.apps.callTool", _p, Void.class); + return caller.invoke("session.mcp.apps.callTool", _p, JsonNode.class); } /** diff --git a/java/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/src/test/java/com/github/copilot/RpcWrappersTest.java index 7b01e1d38..f19d3db01 100644 --- a/java/src/test/java/com/github/copilot/RpcWrappersTest.java +++ b/java/src/test/java/com/github/copilot/RpcWrappersTest.java @@ -389,6 +389,56 @@ void copilotClient_getRpc_throws_before_start() { "getRpc() must throw IllegalStateException if called before start()"); } + // ── session.mcp.apps.callTool tests ─────────────────────────────────────── + + @Test + void sessionRpc_mcp_apps_callTool_invokes_correct_rpc_method() { + var stub = new StubCaller(); + var session = new SessionRpc(stub, "sess-mcp"); + + var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "my-server", "my-tool", + null, null); + session.mcp.apps.callTool(params); + + assertEquals(1, stub.calls.size()); + assertEquals("session.mcp.apps.callTool", stub.calls.get(0).method()); + } + + @Test + void sessionRpc_mcp_apps_callTool_injects_sessionId() { + var stub = new StubCaller(); + var session = new SessionRpc(stub, "sess-ct-inject"); + + var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "server1", "tool1", null, + null); + session.mcp.apps.callTool(params); + + var sentParams = stub.calls.get(0).params(); + assertInstanceOf(com.fasterxml.jackson.databind.node.ObjectNode.class, sentParams); + var node = (com.fasterxml.jackson.databind.node.ObjectNode) sentParams; + assertEquals("sess-ct-inject", node.get("sessionId").asText()); + } + + @Test + void sessionRpc_mcp_apps_callTool_returns_jsonNode_payload() throws Exception { + var stub = new StubCaller(); + var mapper = new ObjectMapper(); + var expectedResult = mapper.createObjectNode(); + expectedResult.put("content", "hello world"); + expectedResult.put("isError", false); + stub.nextResult = expectedResult; + + var session = new SessionRpc(stub, "sess-payload"); + var params = new com.github.copilot.generated.rpc.SessionMcpAppsCallToolParams(null, "echo-server", "echo", + null, null); + var future = session.mcp.apps.callTool(params); + + var result = future.get(); + assertInstanceOf(com.fasterxml.jackson.databind.JsonNode.class, result); + assertEquals("hello world", result.get("content").asText()); + assertEquals(false, result.get("isError").asBoolean()); + } + /** * Helper that creates a loopback socket pair. The client side is used by * {@link JsonRpcClient}; the server side can be read to inspect outbound