prompt.go

  1package prompt
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"sync"
  9
 10	"github.com/opencode-ai/opencode/internal/config"
 11	"github.com/opencode-ai/opencode/internal/llm/models"
 12	"github.com/opencode-ai/opencode/internal/logging"
 13)
 14
 15func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string {
 16	basePrompt := ""
 17	switch agentName {
 18	case config.AgentCoder:
 19		basePrompt = CoderPrompt(provider)
 20	case config.AgentTitle:
 21		basePrompt = TitlePrompt(provider)
 22	case config.AgentTask:
 23		basePrompt = TaskPrompt(provider)
 24	default:
 25		basePrompt = "You are a helpful assistant"
 26	}
 27
 28	if agentName == config.AgentCoder || agentName == config.AgentTask {
 29		// Add context from project-specific instruction files if they exist
 30		contextContent := getContextFromPaths()
 31		logging.Debug("Context content", "Context", contextContent)
 32		if contextContent != "" {
 33			return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
 34		}
 35	}
 36	return basePrompt
 37}
 38
 39var (
 40	onceContext    sync.Once
 41	contextContent string
 42)
 43
 44func getContextFromPaths() string {
 45	onceContext.Do(func() {
 46		var (
 47			cfg          = config.Get()
 48			workDir      = cfg.WorkingDir
 49			contextPaths = cfg.ContextPaths
 50		)
 51
 52		contextContent = processContextPaths(workDir, contextPaths)
 53	})
 54
 55	return contextContent
 56}
 57
 58func processContextPaths(workDir string, paths []string) string {
 59	var (
 60		wg       sync.WaitGroup
 61		resultCh = make(chan string)
 62	)
 63
 64	// Track processed files to avoid duplicates
 65	processedFiles := make(map[string]bool)
 66	var processedMutex sync.Mutex
 67
 68	for _, path := range paths {
 69		wg.Add(1)
 70		go func(p string) {
 71			defer wg.Done()
 72
 73			if strings.HasSuffix(p, "/") {
 74				filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
 75					if err != nil {
 76						return err
 77					}
 78					if !d.IsDir() {
 79						// Check if we've already processed this file (case-insensitive)
 80						processedMutex.Lock()
 81						lowerPath := strings.ToLower(path)
 82						if !processedFiles[lowerPath] {
 83							processedFiles[lowerPath] = true
 84							processedMutex.Unlock()
 85
 86							if result := processFile(path); result != "" {
 87								resultCh <- result
 88							}
 89						} else {
 90							processedMutex.Unlock()
 91						}
 92					}
 93					return nil
 94				})
 95			} else {
 96				fullPath := filepath.Join(workDir, p)
 97
 98				// Check if we've already processed this file (case-insensitive)
 99				processedMutex.Lock()
100				lowerPath := strings.ToLower(fullPath)
101				if !processedFiles[lowerPath] {
102					processedFiles[lowerPath] = true
103					processedMutex.Unlock()
104
105					result := processFile(fullPath)
106					if result != "" {
107						resultCh <- result
108					}
109				} else {
110					processedMutex.Unlock()
111				}
112			}
113		}(path)
114	}
115
116	go func() {
117		wg.Wait()
118		close(resultCh)
119	}()
120
121	results := make([]string, 0)
122	for result := range resultCh {
123		results = append(results, result)
124	}
125
126	return strings.Join(results, "\n")
127}
128
129func processFile(filePath string) string {
130	content, err := os.ReadFile(filePath)
131	if err != nil {
132		return ""
133	}
134	return "# From:" + filePath + "\n" + string(content)
135}