Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ public boolean process(Set<? extends TypeElement> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down