diff --git a/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java b/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java index 9bf4e99c03..2717fe5834 100644 --- a/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java +++ b/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java @@ -127,6 +127,24 @@ public boolean process(Set annotations, RoundEnvironment } } } + + // Validate blank @CopilotToolParam descriptions (exempt single-record wrappers) + boolean isSingleRecordWrapper = schemaParameters.size() == 1 && isRecord(schemaParameters.get(0).asType()); + for (VariableElement param : schemaParameters) { + if (isSingleRecordWrapper && param.equals(schemaParameters.get(0))) { + continue; + } + CopilotToolParam paramAnnotation = param.getAnnotation(CopilotToolParam.class); + if (paramAnnotation != null && paramAnnotation.value().isBlank()) { + TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam on parameter '" + param.getSimpleName() + "' in '" + + enclosingClass.getSimpleName() + "." + method.getSimpleName() + + "' has a blank value (description). " + + "Descriptions are required so the LLM can correctly select and invoke the tool", + param); + } + } } // Group methods by enclosing type diff --git a/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java b/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java index c2bda9f9ad..e557ffdd0b 100644 --- a/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java +++ b/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java @@ -189,6 +189,106 @@ public String search(@CopilotToolParam(value = "Search input", required = false, "Expected compile error for single-record wrapper metadata overrides, got: " + result.diagnostics); } + // ── Test: Blank @CopilotToolParam description validation ──────────────────── + + @Test + void emitsError_forBlankParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class BlankDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam("") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.BlankDescTools", source))); + + assertTrue(hasErrorContaining(result, "blank value (description)"), + "Expected compile error for blank @CopilotToolParam description, got: " + result.diagnostics); + } + + @Test + void emitsError_forWhitespaceOnlyParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class WhitespaceDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam(" ") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.WhitespaceDescTools", source))); + + assertTrue(hasErrorContaining(result, "blank value (description)"), + "Expected compile error for whitespace-only @CopilotToolParam description, got: " + result.diagnostics); + } + + @Test + void compilesSuccessfully_forValidParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class ValidDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam("Search query") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.ValidDescTools", source))); + + assertNoErrors(result); + } + + @Test + void compilesSuccessfully_forParamWithoutAnnotation() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + public class NoAnnotationTools { + @CopilotTool("Search for items") + public String searchItems(String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.NoAnnotationTools", source))); + + assertNoErrors(result); + } + + @Test + void doesNotEmitBlankError_forSingleRecordWrapperWithDefaultAnnotation() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class RecordWrapperTools { + public record SearchArgs(String query, int limit) {} + @CopilotTool("Search for items") + public String search(@CopilotToolParam SearchArgs args) { + return args.query(); + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.RecordWrapperTools", source))); + + assertFalse(hasErrorContaining(result, "blank value (description)"), + "Single-record wrapper should be exempt from blank description check, got: " + result.diagnostics); + } + // ── Test: Return type handling ────────────────────────────────────────────── @Test