Plandex_backup / app /cli /lib /context_shared.go
google-labs-jules[bot]
Final deployment for HF with landing page
93d826e
package lib
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"plandex-cli/api"
"strings"
"sync"
shared "plandex-shared"
"github.com/sashabaranov/go-openai"
)
const ContextMapMaxClientConcurrency = 250
type filePathWithSize struct {
Path string
Size int64
}
type mapFileDetails struct {
size int64
tokens int
shaVal string
mapFilesSkippedAfterSizeLimit []string
mapFilesTruncatedTooLarge []filePathWithSize
mapContent string
}
func getMapFileDetails(path string, size, mapSize int64) (mapFileDetails, error) {
var isImage bool
var totalMapSizeExceeded bool
res := mapFileDetails{
size: size,
mapFilesSkippedAfterSizeLimit: []string{},
mapFilesTruncatedTooLarge: []filePathWithSize{},
}
if !shared.HasFileMapSupport(path) {
if shared.IsImageFile(path) {
isImage = true
var err error
res.tokens, err = readImageTokensForDefsOnly(path, size, openai.ImageURLDetailHigh, 8*1024)
if err != nil {
return mapFileDetails{}, fmt.Errorf("failed to read image tokens for %s: %v", path, err)
}
} else {
res.tokens = shared.GetBytesToTokensEstimate(size)
}
} else {
var truncated bool
if size > shared.MaxContextMapSingleInputSize {
size = shared.MaxContextMapSingleInputSize
truncated = true
res.tokens = shared.GetBytesToTokensEstimate(size)
}
// should go in either skip list *or* truncated list, not both
if mapSize+size > shared.MaxContextMapTotalInputSize {
totalMapSizeExceeded = true
res.mapFilesSkippedAfterSizeLimit = append(res.mapFilesSkippedAfterSizeLimit, path)
res.tokens = shared.GetBytesToTokensEstimate(size)
} else if truncated {
res.mapFilesTruncatedTooLarge = append(res.mapFilesTruncatedTooLarge, filePathWithSize{Path: path, Size: size})
}
}
if totalMapSizeExceeded || !shared.HasFileMapSupport(path) || isImage {
shaVal := sha256.Sum256([]byte(fmt.Sprintf("%d", res.tokens)))
res.shaVal = hex.EncodeToString(shaVal[:])
res.mapContent = ""
res.size = 0
} else {
// partial read for the map
contentRes, err := getMapFileContent(path)
if err != nil {
return mapFileDetails{}, fmt.Errorf("failed to read file %s: %v", path, err)
}
res.mapContent = contentRes.content
res.shaVal = contentRes.shaVal
if contentRes.truncated {
res.mapFilesTruncatedTooLarge = append(res.mapFilesTruncatedTooLarge, filePathWithSize{Path: path, Size: shared.MaxContextMapSingleInputSize})
res.size = shared.MaxContextMapSingleInputSize
res.tokens = shared.GetBytesToTokensEstimate(shared.MaxContextMapSingleInputSize)
} else {
// do the actual token count if we didn't truncate
res.tokens = shared.GetNumTokensEstimate(res.mapContent)
}
}
return res, nil
}
type mapFileContent struct {
mapData []byte
content string
shaVal string
truncated bool
}
func getMapFileContent(path string) (mapFileContent, error) {
f, err := os.Open(path)
if err != nil {
return mapFileContent{}, err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return mapFileContent{}, err
}
size := info.Size()
limit := int64(shared.MaxContextMapSingleInputSize)
truncated := size > limit
limitReader := io.LimitReader(f, limit)
bytes, err := io.ReadAll(limitReader)
if err != nil {
return mapFileContent{}, err
}
sum := sha256.Sum256(bytes)
shaVal := hex.EncodeToString(sum[:])
return mapFileContent{mapData: bytes, content: string(bytes), shaVal: shaVal, truncated: truncated}, nil
}
func processMapBatches(mapInputBatches []shared.FileMapInputs) (shared.FileMapBodies, error) {
allMapBodies := shared.FileMapBodies{}
var mapMu sync.Mutex
errCh := make(chan error, len(mapInputBatches))
for _, batch := range mapInputBatches {
if len(batch) == 0 {
errCh <- nil
continue
}
go func(batch shared.FileMapInputs) {
mapRes, apiErr := api.Client.GetFileMap(shared.GetFileMapRequest{
MapInputs: batch,
})
if apiErr != nil {
errCh <- fmt.Errorf("failed to get file map: %v", apiErr)
return
}
mapMu.Lock()
for path, bodies := range mapRes.MapBodies {
allMapBodies[path] = bodies
}
mapMu.Unlock()
errCh <- nil
}(batch)
}
for i := 0; i < len(mapInputBatches); i++ {
err := <-errCh
if err != nil {
return nil, err
}
}
return allMapBodies, nil
}
func readImageTokensForDefsOnly(path string, size int64, detail openai.ImageURLDetail, headerBytes int64) (int, error) {
file, err := os.Open(path)
if err != nil {
return 0, fmt.Errorf("failed to open file %s: %w", path, err)
}
defer file.Close()
tokens, err := shared.GetImageTokensFromHeader(file, detail, headerBytes)
if err != nil {
tokens = shared.GetImageTokensEstimateFromBytes(size)
}
return tokens, nil
}
func printSkippedFilesMsg(
filesSkippedTooLarge []filePathWithSize,
filesSkippedAfterSizeLimit []string,
mapFilesTruncatedTooLarge []filePathWithSize,
mapFilesSkippedAfterSizeLimit []string,
) {
fmt.Println()
fmt.Println(getSkippedFilesMsg(filesSkippedTooLarge, filesSkippedAfterSizeLimit, mapFilesTruncatedTooLarge, mapFilesSkippedAfterSizeLimit))
}
func getSkippedFilesMsg(
filesSkippedTooLarge []filePathWithSize,
filesSkippedAfterSizeLimit []string,
mapFilesTruncatedTooLarge []filePathWithSize,
mapFilesSkippedAfterSizeLimit []string,
) string {
var builder strings.Builder
if len(filesSkippedTooLarge) > 0 {
fmt.Fprintf(&builder, "ℹ️ These files were skipped because they're too large:\n")
for i, file := range filesSkippedTooLarge {
if i >= maxSkippedFileList {
fmt.Fprintf(&builder, " • and %d more\n", len(filesSkippedTooLarge)-maxSkippedFileList)
break
}
fmt.Fprintf(&builder, " • %s - %d MB\n", file.Path, file.Size/1024/1024)
}
}
if len(mapFilesTruncatedTooLarge) > 0 {
fmt.Fprintf(&builder, "ℹ️ These files were truncated because they're too large to map fully:\n")
for i, file := range mapFilesTruncatedTooLarge {
if i >= maxSkippedFileList {
fmt.Fprintf(&builder, " • and %d more\n", len(mapFilesTruncatedTooLarge)-maxSkippedFileList)
break
}
if file.Size > 1024*1024 {
fmt.Fprintf(&builder, " • %s - %d MB\n", file.Path, file.Size/1024/1024)
} else {
fmt.Fprintf(&builder, " • %s - %d KB\n", file.Path, file.Size/1024)
}
}
if len(mapFilesTruncatedTooLarge) > 0 {
fmt.Fprintf(&builder, "They will still be included in the map, but only the first %d KB will be mapped.\n", shared.MaxContextMapSingleInputSize/1024)
}
}
if len(filesSkippedAfterSizeLimit) > 0 {
fmt.Fprintf(&builder, "ℹ️ These files were skipped because the total size limit was exceeded:\n")
for i, file := range filesSkippedAfterSizeLimit {
if i >= maxSkippedFileList {
fmt.Fprintf(&builder, " • and %d more\n", len(filesSkippedAfterSizeLimit)-maxSkippedFileList)
break
}
fmt.Fprintf(&builder, " • %s\n", file)
}
}
if len(mapFilesSkippedAfterSizeLimit) > 0 {
fmt.Fprintf(&builder, "ℹ️ These files were skipped because the total map size limit was exceeded:\n")
for i, file := range mapFilesSkippedAfterSizeLimit {
if i >= maxSkippedFileList {
fmt.Fprintf(&builder, " • and %d more\n", len(mapFilesSkippedAfterSizeLimit)-maxSkippedFileList)
break
}
fmt.Fprintf(&builder, " • %s\n", file)
}
if len(mapFilesSkippedAfterSizeLimit) > 0 {
fmt.Fprintf(&builder, "They will still be included in the map as paths in the project, but no maps will be generated for them.\n")
}
}
return builder.String()
}