161 lines
4.1 KiB
Go
161 lines
4.1 KiB
Go
package lib
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"plandex-cli/api"
|
|
"plandex-cli/types"
|
|
shared "plandex-shared"
|
|
"sync"
|
|
|
|
"github.com/sashabaranov/go-openai"
|
|
)
|
|
|
|
func AutoLoadContextFiles(ctx context.Context, files []string) (string, error) {
|
|
contexts, err := api.Client.ListContext(CurrentPlanId, CurrentBranch)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get contexts: %v", err)
|
|
}
|
|
|
|
var totalSize int64
|
|
totalContexts := len(contexts)
|
|
|
|
for _, context := range contexts {
|
|
totalSize += context.BodySize
|
|
}
|
|
|
|
loadContextReqsByIndex := make(map[int]*shared.LoadContextParams)
|
|
filesSkippedTooLarge := []filePathWithSize{}
|
|
filesSkippedAfterSizeLimit := []string{}
|
|
|
|
var mu sync.Mutex
|
|
errCh := make(chan error, len(files))
|
|
|
|
for i, path := range files {
|
|
totalContexts++
|
|
if totalContexts > shared.MaxContextCount {
|
|
log.Println("Skipping file", path, "because it would exceed the max context count", totalContexts)
|
|
filesSkippedAfterSizeLimit = append(filesSkippedAfterSizeLimit, path)
|
|
errCh <- nil
|
|
continue
|
|
}
|
|
|
|
go func(index int, path string) {
|
|
fileInfo, err := os.Stat(path)
|
|
if err != nil {
|
|
errCh <- fmt.Errorf("failed to get file info for %s: %v", path, err)
|
|
return
|
|
}
|
|
|
|
if fileInfo.IsDir() {
|
|
log.Println("Skipping directory", path)
|
|
errCh <- nil // skip directories
|
|
return
|
|
}
|
|
|
|
size := fileInfo.Size()
|
|
|
|
mu.Lock()
|
|
if size > shared.MaxContextBodySize {
|
|
log.Println("Skipping file", path, "because it's too large", size)
|
|
filesSkippedTooLarge = append(filesSkippedTooLarge, filePathWithSize{Path: path, Size: size})
|
|
mu.Unlock()
|
|
errCh <- nil
|
|
return
|
|
}
|
|
if totalSize+size > shared.MaxTotalContextSize {
|
|
log.Println("Skipping file", path, "because it would exceed the max context body size", totalSize+size)
|
|
filesSkippedAfterSizeLimit = append(filesSkippedAfterSizeLimit, path)
|
|
mu.Unlock()
|
|
errCh <- nil
|
|
return
|
|
}
|
|
totalSize += size
|
|
mu.Unlock()
|
|
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
errCh <- fmt.Errorf("failed to read file %s: %v", path, err)
|
|
return
|
|
}
|
|
|
|
var contextType shared.ContextType
|
|
isImage := shared.IsImageFile(path)
|
|
if isImage {
|
|
contextType = shared.ContextImageType
|
|
} else {
|
|
contextType = shared.ContextFileType
|
|
}
|
|
|
|
var imageDetail openai.ImageURLDetail
|
|
if isImage {
|
|
imageDetail = openai.ImageURLDetailHigh
|
|
}
|
|
|
|
var body string
|
|
if isImage {
|
|
body = base64.StdEncoding.EncodeToString(b)
|
|
} else {
|
|
body = string(shared.NormalizeEOL(b))
|
|
}
|
|
|
|
mu.Lock()
|
|
loadContextReqsByIndex[index] = &shared.LoadContextParams{
|
|
ContextType: contextType,
|
|
FilePath: path,
|
|
Name: path,
|
|
Body: body,
|
|
AutoLoaded: true,
|
|
ImageDetail: imageDetail,
|
|
}
|
|
mu.Unlock()
|
|
errCh <- nil
|
|
}(i, path)
|
|
}
|
|
|
|
for range files {
|
|
if e := <-errCh; e != nil {
|
|
return "", fmt.Errorf("failed to load context: %v", e)
|
|
}
|
|
}
|
|
|
|
// Convert map back to ordered slice
|
|
loadContextReqs := make(shared.LoadContextRequest, 0, len(loadContextReqsByIndex))
|
|
for i := 0; i < len(files); i++ {
|
|
if req := loadContextReqsByIndex[i]; req != nil {
|
|
loadContextReqs = append(loadContextReqs, req)
|
|
}
|
|
}
|
|
|
|
// even if there are no files to load, we still need to hit the API endpoint because the stream is waiting on a channel for the autoload to finish
|
|
res, apiErr := api.Client.AutoLoadContext(ctx, CurrentPlanId, CurrentBranch, loadContextReqs)
|
|
if apiErr != nil {
|
|
return "", fmt.Errorf("failed to load context: %v", apiErr.Msg)
|
|
}
|
|
|
|
if res.MaxTokensExceeded {
|
|
overage := res.TotalTokens - res.MaxTokens
|
|
return "", fmt.Errorf("update would add %d 🪙 and exceed token limit (%d) by %d 🪙", res.TokensAdded, res.MaxTokens, overage)
|
|
}
|
|
|
|
msg := res.Msg
|
|
|
|
// Print skip info if any
|
|
if len(filesSkippedTooLarge) > 0 || len(filesSkippedAfterSizeLimit) > 0 {
|
|
msg += "\n\n" + getSkippedFilesMsg(filesSkippedTooLarge, filesSkippedAfterSizeLimit, nil, nil)
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
func MustLoadAutoContextMap() {
|
|
MustLoadContext([]string{"."}, &types.LoadContextParams{
|
|
DefsOnly: true,
|
|
SkipIgnoreWarning: true,
|
|
AutoLoaded: true,
|
|
})
|
|
}
|