package prompt import ( "fmt" "os" "path/filepath" "strings" "sync" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/llm/models" "log/slog" ) func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string { basePrompt := "" switch agentName { case config.AgentPrimary: basePrompt = PrimaryPrompt(provider) case config.AgentTitle: basePrompt = TitlePrompt(provider) case config.AgentTask: basePrompt = TaskPrompt(provider) default: basePrompt = "You are a helpful assistant" } if agentName == config.AgentPrimary || agentName == config.AgentTask { // Add context from project-specific instruction files if they exist contextContent := getContextFromPaths() slog.Debug("Context content", "Context", contextContent) if contextContent != "" { return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent) } } return basePrompt } var ( onceContext sync.Once contextContent string ) func getContextFromPaths() string { onceContext.Do(func() { var ( cfg = config.Get() workDir = cfg.WorkingDir contextPaths = cfg.ContextPaths ) contextContent = processContextPaths(workDir, contextPaths) }) return contextContent } func processContextPaths(workDir string, paths []string) string { var ( wg sync.WaitGroup resultCh = make(chan string) ) // Track processed files to avoid duplicates processedFiles := make(map[string]bool) var processedMutex sync.Mutex for _, path := range paths { wg.Add(1) go func(p string) { defer wg.Done() if strings.HasSuffix(p, "/") { filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error { if err != nil { return err } if !d.IsDir() { // Check if we've already processed this file (case-insensitive) processedMutex.Lock() lowerPath := strings.ToLower(path) if !processedFiles[lowerPath] { processedFiles[lowerPath] = true processedMutex.Unlock() if result := processFile(path); result != "" { resultCh <- result } } else { processedMutex.Unlock() } } return nil }) } else { fullPath := filepath.Join(workDir, p) // Check if we've already processed this file (case-insensitive) processedMutex.Lock() lowerPath := strings.ToLower(fullPath) if !processedFiles[lowerPath] { processedFiles[lowerPath] = true processedMutex.Unlock() result := processFile(fullPath) if result != "" { resultCh <- result } } else { processedMutex.Unlock() } } }(path) } go func() { wg.Wait() close(resultCh) }() results := make([]string, 0) for result := range resultCh { results = append(results, result) } return strings.Join(results, "\n") } func processFile(filePath string) string { content, err := os.ReadFile(filePath) if err != nil { return "" } return "# From:" + filePath + "\n" + string(content) }