Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
51d2688
Initial commit
luis-gasparschroeder Sep 20, 2025
5c8f4ab
Added tests
luis-gasparschroeder Sep 20, 2025
24f6f60
Implemented endpoint
luis-gasparschroeder Sep 20, 2025
d69d9c0
Merged HyperionRepositoryStructureService into HyperionProgrammingExe…
luis-gasparschroeder Sep 23, 2025
b6ed7ca
Removed service annotation
luis-gasparschroeder Sep 23, 2025
13fe6e5
Added Service suffix
luis-gasparschroeder Sep 23, 2025
2eced3f
Updated API
luis-gasparschroeder Sep 23, 2025
4e7a2ab
Using signal
luis-gasparschroeder Sep 23, 2025
40e8f35
Moved getExistingSolutionCode to cenralized class
luis-gasparschroeder Sep 23, 2025
da81272
Moved getExistingSolutionCode to cenralized class
luis-gasparschroeder Sep 23, 2025
236bbe0
Implemented module auto generation
luis-gasparschroeder Sep 24, 2025
8473a1d
Fixed signal bug
luis-gasparschroeder Sep 25, 2025
1e1dc1f
Added service defintions (required for discovery)
luis-gasparschroeder Sep 26, 2025
525510c
Adjusted prompt injection to adhere to HyperionPromptTemplateService
luis-gasparschroeder Sep 26, 2025
0c171c7
Added i18n localization
luis-gasparschroeder Sep 26, 2025
9667bb4
Added documentation
luis-gasparschroeder Sep 26, 2025
53ecb19
Removed generateSolutionPlan duplicate
luis-gasparschroeder Sep 26, 2025
c78609c
Dependency injecting beans
luis-gasparschroeder Sep 26, 2025
3884102
Fixed creation assistance button alignment
luis-gasparschroeder Sep 26, 2025
1ea753f
Merge branch 'develop' into feature/hyperion/code-generation-clean
luis-gasparschroeder Sep 29, 2025
beb8b29
Added code generation documentation
luis-gasparschroeder Sep 29, 2025
978e204
Modified code generation documentation
luis-gasparschroeder Sep 30, 2025
c91763a
Hide code generation button when hyperion is disabled
luis-gasparschroeder Sep 30, 2025
2cb1ca4
Merge branch 'develop' into feature/hyperion/code-generation-clean
FelixTJDietrich Sep 30, 2025
ea8f9f6
Fix constructor calls in Hyperion test files
luis-gasparschroeder Oct 2, 2025
620a048
Merge branch 'feature/hyperion/code-generation-clean' of github.com:l…
luis-gasparschroeder Oct 2, 2025
595252a
Fixed DTO naming conventions
luis-gasparschroeder Oct 2, 2025
3416a0f
Added docstring
luis-gasparschroeder Oct 2, 2025
f1a1316
Fixed tests
luis-gasparschroeder Oct 2, 2025
433d89c
Added additional tests to increase code coverage
luis-gasparschroeder Oct 2, 2025
c0637bc
Adjusted jest config
luis-gasparschroeder Oct 3, 2025
f0fc096
Merge branch 'develop' into feature/hyperion/code-generation-clean
luis-gasparschroeder Oct 6, 2025
47575dc
Removed old logic
luis-gasparschroeder Oct 6, 2025
020657a
Updated dependencies
luis-gasparschroeder Oct 6, 2025
2d63c25
Implemented tests to increase code coverage
luis-gasparschroeder Oct 6, 2025
e96dcae
Updated test naming
luis-gasparschroeder Oct 6, 2025
b9da96a
Removed duplicated tests
luis-gasparschroeder Oct 6, 2025
e795c98
Removed duplicated tests
luis-gasparschroeder Oct 6, 2025
0f10c11
Fixed architecture-related tests
luis-gasparschroeder Oct 6, 2025
530bc25
Merge branch 'develop' into feature/hyperion/code-generation-clean
sawys777 Oct 21, 2025
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 @@ -450,13 +450,29 @@ private void addDirectory(String containerId, String directoryName, boolean crea
}

private void copyToContainer(Path sourcePath, String containerId) {
try (final var uploadStream = new ByteArrayInputStream(createTarArchive(sourcePath).toByteArray());
final var copyToContainerCommand = buildAgentConfiguration.getDockerClient().copyArchiveToContainerCmd(containerId)
.withRemotePath(LOCAL_CI_DOCKER_CONTAINER_WORKING_DIRECTORY).withTarInputStream(uploadStream)) {
copyToContainerCommand.exec();
}
catch (IOException e) {
throw new LocalCIException("Could not copy to container " + containerId, e);
final int maxRetries = 3;
final long retryDelayMs = 1000;

for (int attempt = 1; attempt <= maxRetries; attempt++) {
try (final var uploadStream = new ByteArrayInputStream(createTarArchive(sourcePath).toByteArray());
final var copyToContainerCommand = buildAgentConfiguration.getDockerClient().copyArchiveToContainerCmd(containerId)
.withRemotePath(LOCAL_CI_DOCKER_CONTAINER_WORKING_DIRECTORY).withTarInputStream(uploadStream)) {
copyToContainerCommand.exec();
return;
}
catch (Exception e) {
if (attempt == maxRetries) {
throw new LocalCIException("Could not copy to container " + containerId + " after " + maxRetries + " attempts", e);
}
log.warn("Attempt {} failed to copy to container {}: {}. Retrying in {} ms...", attempt, containerId, e.getMessage(), retryDelayMs);
try {
Thread.sleep(retryDelayMs);
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new LocalCIException("Interrupted while retrying copy to container " + containerId, ie);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
package de.tum.cit.aet.artemis.hyperion.dto;

import com.fasterxml.jackson.annotation.JsonValue;

/**
* Enum for artifact types in consistency checks.
*/
public enum ArtifactType {
PROBLEM_STATEMENT, TEMPLATE_REPOSITORY, SOLUTION_REPOSITORY, TESTS_REPOSITORY

PROBLEM_STATEMENT("PROBLEM_STATEMENT"), TEMPLATE_REPOSITORY("TEMPLATE_REPOSITORY"), SOLUTION_REPOSITORY("SOLUTION_REPOSITORY"), TESTS_REPOSITORY("TESTS_REPOSITORY");

private final String value;

ArtifactType(String value) {
this.value = value;
}

@JsonValue
public String getValue() {
return value;
}

@Override
public String toString() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package de.tum.cit.aet.artemis.hyperion.dto;

import jakarta.validation.constraints.NotNull;

import com.fasterxml.jackson.annotation.JsonInclude;

import de.tum.cit.aet.artemis.programming.domain.RepositoryType;

/**
* DTO for requesting code generation for a programming exercise.
* Contains the repository type to determine which generation strategy to use.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record CodeGenerationRequestDTO(@NotNull RepositoryType repositoryType) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.tum.cit.aet.artemis.hyperion.dto;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* DTO for internal code generation response.
* Contains the generated content from AI service calls.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record CodeGenerationResponseDTO(
/**
* The solution plan as a string
*/
String solutionPlan,

/**
* List of generated files with path and content
*/
List<GeneratedFile> files) {

public String getSolutionPlan() {
return solutionPlan;
}

public List<GeneratedFile> getFiles() {
return files;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.tum.cit.aet.artemis.hyperion.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* DTO for REST API code generation result.
* Contains the result of the code generation and compilation process.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record CodeGenerationResultDTO(
/**
* Whether the code generation and compilation was successful
*/
boolean success,

/**
* Descriptive message about the generation result
*/
String message,

/**
* Number of attempts made during the generation process
*/
int attempts) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package de.tum.cit.aet.artemis.hyperion.dto;

public record GeneratedFile(String path, String content) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public class HyperionProgrammingExerciseContextRendererService {

private static final Logger log = LoggerFactory.getLogger(HyperionProgrammingExerciseContextRendererService.class);

private static final Set<String> EXCLUDED_DIRECTORIES = Set.of(".git", ".idea", ".vscode", "target", "build", "out", "bin", "node_modules", ".gradle", ".mvn", "dist", ".next",
"coverage");

private static final Set<String> EXCLUDED_FILES = Set.of(".DS_Store", "Thumbs.db", ".gitkeep");

private final RepositoryService repositoryService;

private final HyperionProgrammingLanguageContextFilterService languageFilter;
Expand Down Expand Up @@ -227,4 +232,79 @@ private static String renderFileString(String root, String path, String content)
});
return String.join("\n", out);
}

/**
* Generates a tree-format representation of the repository structure.
* Reads the current state fresh each time to capture any changes.
*
* @param repository the repository to analyze
* @return tree-format string representation of the repository structure
*/
public String getRepositoryStructure(Repository repository) {
try {
File repositoryRoot = repository.getLocalPath().toFile();
if (!repositoryRoot.exists() || !repositoryRoot.isDirectory()) {
log.warn("Repository path does not exist or is not a directory: {}", repositoryRoot.getAbsolutePath());
return "Repository structure could not be determined.";
}

StringBuilder structure = new StringBuilder();
structure.append(repositoryRoot.getName()).append("/").append("\n");
generateTreeStructure(repositoryRoot, structure, "", true);

return structure.toString();

}
catch (Exception e) {
log.error("Failed to generate repository structure for repository: {}", repository.getLocalPath(), e);
return "Repository structure could not be determined due to an error.";
}
}

/**
* Recursively generates the tree structure representation.
*
* @param directory the current directory to process
* @param structure the StringBuilder to append to
* @param prefix the current line prefix for proper tree formatting
* @param isLast whether this is the last item in its parent directory
*/
private void generateTreeStructure(File directory, StringBuilder structure, String prefix, boolean isLast) {
File[] files = directory.listFiles();
if (files == null) {
return;
}

// Filter out excluded directories and files
File[] filteredFiles = Arrays.stream(files).filter(file -> !EXCLUDED_DIRECTORIES.contains(file.getName()) && !EXCLUDED_FILES.contains(file.getName())).sorted((a, b) -> {
// Directories first, then files
if (a.isDirectory() && !b.isDirectory()) {
return -1;
}
else if (!a.isDirectory() && b.isDirectory()) {
return 1;
}
return a.getName().compareToIgnoreCase(b.getName());
}).toArray(File[]::new);

for (int i = 0; i < filteredFiles.length; i++) {
File file = filteredFiles[i];
boolean isLastFile = (i == filteredFiles.length - 1);

structure.append(prefix);
structure.append(isLastFile ? "└── " : "├── ");
structure.append(file.getName());

if (file.isDirectory()) {
structure.append("/");
}
structure.append("\n");

// Recursively process subdirectories
if (file.isDirectory()) {
String newPrefix = prefix + (isLastFile ? " " : "│ ");
generateTreeStructure(file, structure, newPrefix, false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,27 @@ public String render(String resourcePath, Map<String, String> variables) {
throw new RuntimeException("Failed to load template: " + resourcePath, e);
}
}

/**
* Render the template at the given classpath resource path with the provided variables.
* <p>
* Supporting placeholders of the form {{var}}
*
* @param resourcePath classpath to the template resource
* @param variables map of variables used during rendering
* @return the rendered string
*/
public String renderObject(String resourcePath, Map<String, Object> variables) {
try {
var resource = new ClassPathResource(resourcePath);
String rendered = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
for (var entry : variables.entrySet()) {
rendered = rendered.replace("{{" + entry.getKey() + "}}", entry.getValue().toString());
}
return rendered;
}
catch (IOException e) {
throw new RuntimeException("Failed to load template: " + resourcePath, e);
}
}
}
Loading
Loading