diff --git a/pkg/execution/engine.go b/pkg/execution/engine.go index fa4a5b7..f763167 100644 --- a/pkg/execution/engine.go +++ b/pkg/execution/engine.go @@ -395,15 +395,25 @@ func (e *DefaultTaskExecutionEngine) executeSandboxCommands(ctx context.Context, // createSandboxConfig creates a sandbox configuration from task requirements func (e *DefaultTaskExecutionEngine) createSandboxConfig(request *TaskExecutionRequest) *SandboxConfig { + // Use image selector to choose appropriate development environment + imageSelector := NewImageSelector() + selectedImage := imageSelector.SelectImageForTask(request) + config := &SandboxConfig{ Type: "docker", - Image: "alpine:latest", + Image: selectedImage, // Auto-selected based on task language Architecture: "amd64", - WorkingDir: "/workspace", + WorkingDir: "/workspace/data", // Use standardized workspace structure Timeout: 5 * time.Minute, Environment: make(map[string]string), } + // Add standardized workspace environment variables + config.Environment["WORKSPACE_ROOT"] = "/workspace" + config.Environment["WORKSPACE_INPUT"] = "/workspace/input" + config.Environment["WORKSPACE_DATA"] = "/workspace/data" + config.Environment["WORKSPACE_OUTPUT"] = "/workspace/output" + // Apply defaults from engine config if e.config.SandboxDefaults != nil { if e.config.SandboxDefaults.Image != "" { diff --git a/pkg/execution/images.go b/pkg/execution/images.go new file mode 100644 index 0000000..d459dcf --- /dev/null +++ b/pkg/execution/images.go @@ -0,0 +1,256 @@ +package execution + +import ( + "fmt" + "strings" +) + +const ( + // ImageRegistry is the default registry for CHORUS development images + ImageRegistry = "registry.home.deepblack.cloud/chorus" + + // ImageVersion is the default version tag to use + ImageVersion = "latest" +) + +// ImageSelector maps task languages and contexts to appropriate development images +type ImageSelector struct { + registry string + version string +} + +// NewImageSelector creates a new image selector with default settings +func NewImageSelector() *ImageSelector { + return &ImageSelector{ + registry: ImageRegistry, + version: ImageVersion, + } +} + +// NewImageSelectorWithConfig creates an image selector with custom registry and version +func NewImageSelectorWithConfig(registry, version string) *ImageSelector { + if registry == "" { + registry = ImageRegistry + } + if version == "" { + version = ImageVersion + } + return &ImageSelector{ + registry: registry, + version: version, + } +} + +// SelectImage returns the appropriate image name for a given language +func (s *ImageSelector) SelectImage(language string) string { + imageMap := map[string]string{ + "rust": "rust-dev", + "go": "go-dev", + "golang": "go-dev", + "python": "python-dev", + "py": "python-dev", + "javascript": "node-dev", + "js": "node-dev", + "typescript": "node-dev", + "ts": "node-dev", + "node": "node-dev", + "nodejs": "node-dev", + "java": "java-dev", + "cpp": "cpp-dev", + "c++": "cpp-dev", + "c": "cpp-dev", + } + + normalizedLang := strings.ToLower(strings.TrimSpace(language)) + + if img, ok := imageMap[normalizedLang]; ok { + return fmt.Sprintf("%s/%s:%s", s.registry, img, s.version) + } + + // Default to base image if language not recognized + return fmt.Sprintf("%s/base:%s", s.registry, s.version) +} + +// DetectLanguage analyzes task context to determine primary programming language +func (s *ImageSelector) DetectLanguage(task *TaskExecutionRequest) string { + // Priority 1: Explicit language specification + if lang, ok := task.Context["language"].(string); ok && lang != "" { + return strings.ToLower(strings.TrimSpace(lang)) + } + + // Priority 2: Language hint in requirements + if task.Requirements != nil && task.Requirements.AIModel != "" { + // Some models might hint at language in their name + modelLang := extractLanguageFromModel(task.Requirements.AIModel) + if modelLang != "" { + return modelLang + } + } + + // Priority 3: Repository URL analysis + if repoURL, ok := task.Context["repository_url"].(string); ok && repoURL != "" { + return detectLanguageFromRepo(repoURL) + } + + // Priority 4: Description keyword analysis + return detectLanguageFromDescription(task.Description) +} + +// SelectImageForTask is a convenience method that detects language and returns appropriate image +func (s *ImageSelector) SelectImageForTask(task *TaskExecutionRequest) string { + language := s.DetectLanguage(task) + return s.SelectImage(language) +} + +// detectLanguageFromDescription analyzes task description for language keywords +func detectLanguageFromDescription(description string) string { + desc := strings.ToLower(description) + + // Keyword map with priority (specific keywords beat generic ones) + keywords := []struct { + language string + patterns []string + priority int + }{ + // High priority - specific language indicators + {"rust", []string{"rust", "cargo.toml", ".rs file", "rustc", "cargo build"}, 3}, + {"go", []string{"golang", "go.mod", "go.sum", ".go file", "go build"}, 3}, + {"python", []string{"python3", "pip install", ".py file", "pytest", "requirements.txt", "pyproject.toml"}, 3}, + {"typescript", []string{"typescript", ".ts file", "tsconfig.json"}, 3}, + {"javascript", []string{"node.js", "npm install", "package.json", ".js file"}, 2}, + {"java", []string{"java", "maven", "gradle", "pom.xml", ".java file"}, 3}, + {"cpp", []string{"c++", "cmake", ".cpp file", ".cc file", "makefile"}, 3}, + + // Medium priority - generic mentions + {"rust", []string{"rust"}, 2}, + {"go", []string{"go "}, 2}, + {"python", []string{"python"}, 2}, + {"node", []string{"node ", "npm ", "yarn "}, 2}, + {"java", []string{"java "}, 2}, + {"cpp", []string{"c++ ", "cpp "}, 2}, + {"c", []string{" c "}, 1}, + } + + bestMatch := "" + bestPriority := 0 + + for _, kw := range keywords { + for _, pattern := range kw.patterns { + if strings.Contains(desc, pattern) { + if kw.priority > bestPriority { + bestMatch = kw.language + bestPriority = kw.priority + } + break + } + } + } + + if bestMatch != "" { + return bestMatch + } + + return "base" +} + +// detectLanguageFromRepo attempts to detect language from repository URL or name +func detectLanguageFromRepo(repoURL string) string { + repo := strings.ToLower(repoURL) + + // Check for language-specific repository naming patterns + patterns := map[string][]string{ + "rust": {"-rs", ".rs", "rust-"}, + "go": {"-go", ".go", "go-"}, + "python": {"-py", ".py", "python-"}, + "javascript": {"-js", ".js", "node-"}, + "typescript": {"-ts", ".ts"}, + "java": {"-java", ".java"}, + "cpp": {"-cpp", ".cpp", "-cxx"}, + } + + for lang, pats := range patterns { + for _, pat := range pats { + if strings.Contains(repo, pat) { + return lang + } + } + } + + return "base" +} + +// extractLanguageFromModel tries to extract language hints from model name +func extractLanguageFromModel(modelName string) string { + model := strings.ToLower(modelName) + + // Some models are language-specific + if strings.Contains(model, "codellama") { + return "base" // CodeLlama is multi-language + } + if strings.Contains(model, "go") && strings.Contains(model, "coder") { + return "go" + } + if strings.Contains(model, "rust") { + return "rust" + } + if strings.Contains(model, "python") { + return "python" + } + + return "" +} + +// GetAvailableImages returns a list of all available development images +func (s *ImageSelector) GetAvailableImages() []string { + images := []string{"base", "rust-dev", "go-dev", "python-dev", "node-dev", "java-dev", "cpp-dev"} + result := make([]string, len(images)) + + for i, img := range images { + result[i] = fmt.Sprintf("%s/%s:%s", s.registry, img, s.version) + } + + return result +} + +// GetImageInfo returns metadata about a specific image +func (s *ImageSelector) GetImageInfo(imageName string) map[string]string { + infoMap := map[string]map[string]string{ + "base": { + "description": "Base Debian development environment with common tools", + "size": "~200MB", + "tools": "git, curl, build-essential, vim, jq", + }, + "rust-dev": { + "description": "Rust development environment with cargo and tooling", + "size": "~1.2GB", + "tools": "rustc, cargo, clippy, rustfmt, ripgrep, fd-find", + }, + "go-dev": { + "description": "Go development environment with standard tooling", + "size": "~600MB", + "tools": "go1.22, gopls, delve, staticcheck, golangci-lint", + }, + "python-dev": { + "description": "Python development environment with modern tooling", + "size": "~800MB", + "tools": "python3.11, uv, ruff, black, pytest, mypy", + }, + "node-dev": { + "description": "Node.js development environment with package managers", + "size": "~700MB", + "tools": "node20, pnpm, yarn, typescript, eslint, prettier", + }, + "java-dev": { + "description": "Java development environment with build tools", + "size": "~1.5GB", + "tools": "openjdk-17, maven, gradle", + }, + "cpp-dev": { + "description": "C/C++ development environment with compilers and tools", + "size": "~900MB", + "tools": "gcc, g++, clang, cmake, ninja, gdb, valgrind", + }, + } + + return infoMap[imageName] +} \ No newline at end of file