package execution import ( "fmt" "strings" ) const ( // ImageRegistry is the default registry for CHORUS development images ImageRegistry = "anthonyrawlins" // 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": "chorus-rust-dev", "go": "chorus-go-dev", "golang": "chorus-go-dev", "python": "chorus-python-dev", "py": "chorus-python-dev", "javascript": "chorus-node-dev", "js": "chorus-node-dev", "typescript": "chorus-node-dev", "ts": "chorus-node-dev", "node": "chorus-node-dev", "nodejs": "chorus-node-dev", "java": "chorus-java-dev", "cpp": "chorus-cpp-dev", "c++": "chorus-cpp-dev", "c": "chorus-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/chorus-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{"chorus-base", "chorus-rust-dev", "chorus-go-dev", "chorus-python-dev", "chorus-node-dev", "chorus-java-dev", "chorus-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{ "chorus-base": { "description": "Base Debian development environment with common tools", "size": "~643MB", "tools": "git, curl, build-essential, vim, jq", "registry": "docker.io/anthonyrawlins/chorus-base", }, "chorus-rust-dev": { "description": "Rust development environment with cargo and tooling", "size": "~2.42GB", "tools": "rustc, cargo, clippy, rustfmt, ripgrep, fd-find", "registry": "docker.io/anthonyrawlins/chorus-rust-dev", }, "chorus-go-dev": { "description": "Go development environment with standard tooling", "size": "~1GB", "tools": "go1.22, gopls, delve, staticcheck, golangci-lint", "registry": "docker.io/anthonyrawlins/chorus-go-dev", }, "chorus-python-dev": { "description": "Python development environment with modern tooling", "size": "~1.07GB", "tools": "python3.11, uv, ruff, black, pytest, mypy", "registry": "docker.io/anthonyrawlins/chorus-python-dev", }, "chorus-node-dev": { "description": "Node.js development environment with package managers", "size": "~982MB", "tools": "node20, pnpm, yarn, typescript, eslint, prettier", "registry": "docker.io/anthonyrawlins/chorus-node-dev", }, "chorus-java-dev": { "description": "Java development environment with build tools", "size": "~1.3GB", "tools": "openjdk-17, maven, gradle", "registry": "docker.io/anthonyrawlins/chorus-java-dev", }, "chorus-cpp-dev": { "description": "C/C++ development environment with compilers and tools", "size": "~1.63GB", "tools": "gcc, g++, clang, cmake, ninja, gdb, valgrind", "registry": "docker.io/anthonyrawlins/chorus-cpp-dev", }, } return infoMap[imageName] }