Files
LocalAGI/services/actions/githubrepositorygetallcontent.go
Ettore Di Giacinto 5e5224da25 fix(github): skip binary files
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2025-05-02 22:34:37 +02:00

208 lines
6.5 KiB
Go

package actions
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/google/go-github/v69/github"
"github.com/mudler/LocalAGI/core/types"
"github.com/mudler/LocalAGI/pkg/config"
"github.com/mudler/LocalAGI/pkg/xlog"
"github.com/sashabaranov/go-openai/jsonschema"
)
type GithubRepositoryGetAllContent struct {
token, repository, owner, customActionName string
client *github.Client
}
func NewGithubRepositoryGetAllContent(config map[string]string) *GithubRepositoryGetAllContent {
client := github.NewClient(nil).WithAuthToken(config["token"])
return &GithubRepositoryGetAllContent{
client: client,
token: config["token"],
repository: config["repository"],
owner: config["owner"],
customActionName: config["customActionName"],
}
}
// isTextFile checks if a file is likely to be a text file based on its extension
func isTextFile(path string) bool {
// List of common text/code file extensions
textExtensions := map[string]bool{
".txt": true, ".md": true, ".go": true, ".py": true, ".js": true,
".ts": true, ".jsx": true, ".tsx": true, ".html": true, ".css": true,
".json": true, ".yaml": true, ".yml": true, ".xml": true, ".sql": true,
".sh": true, ".bash": true, ".zsh": true, ".rb": true, ".php": true,
".java": true, ".c": true, ".cpp": true, ".h": true, ".hpp": true,
".rs": true, ".swift": true, ".kt": true, ".scala": true, ".lua": true,
".pl": true, ".r": true, ".m": true, ".mm": true, ".f": true,
".f90": true, ".f95": true, ".f03": true, ".f08": true, ".f15": true,
".hs": true, ".lhs": true, ".erl": true, ".hrl": true, ".ex": true,
".exs": true, ".eex": true, ".leex": true, ".heex": true, ".config": true,
".toml": true, ".ini": true, ".conf": true, ".env": true, ".gitignore": true,
".dockerignore": true, ".editorconfig": true, ".prettierrc": true, ".eslintrc": true,
".babelrc": true, ".npmrc": true, ".yarnrc": true, ".lock": true,
}
// Get the file extension
ext := strings.ToLower(filepath.Ext(path))
return textExtensions[ext]
}
func (g *GithubRepositoryGetAllContent) getContentRecursively(ctx context.Context, path string, owner string, repository string) (string, error) {
var result strings.Builder
// Get content at the current path
_, directoryContent, _, err := g.client.Repositories.GetContents(ctx, owner, repository, path, nil)
if err != nil {
return "", fmt.Errorf("error getting content at path %s: %w", path, err)
}
// Process each item in the directory
for _, item := range directoryContent {
if item.GetType() == "dir" {
// Recursively get content for subdirectories
subContent, err := g.getContentRecursively(ctx, item.GetPath(), owner, repository)
if err != nil {
return "", err
}
result.WriteString(subContent)
} else if item.GetType() == "file" {
// Skip binary/image files
if !isTextFile(item.GetPath()) {
xlog.Warn("Skipping non-text file: ", "file", item.GetPath())
result.WriteString(fmt.Sprintf("Skipping non-text file: %s\n", item.GetPath()))
continue
}
// Get file content
fileContent, _, _, err := g.client.Repositories.GetContents(ctx, owner, repository, item.GetPath(), nil)
if err != nil {
return "", fmt.Errorf("error getting file content for %s: %w", item.GetPath(), err)
}
content, err := fileContent.GetContent()
if err != nil {
return "", fmt.Errorf("error decoding content for %s: %w", item.GetPath(), err)
}
// Add file content to result with clear markers
result.WriteString(fmt.Sprintf("\n--- START FILE: %s ---\n", item.GetPath()))
result.WriteString(content)
result.WriteString(fmt.Sprintf("\n--- END FILE: %s ---\n", item.GetPath()))
}
}
return result.String(), nil
}
func (g *GithubRepositoryGetAllContent) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
result := struct {
Repository string `json:"repository"`
Owner string `json:"owner"`
Path string `json:"path,omitempty"`
}{}
err := params.Unmarshal(&result)
if err != nil {
return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err)
}
if g.repository != "" && g.owner != "" {
result.Repository = g.repository
result.Owner = g.owner
}
// Start from root if no path specified
if result.Path == "" {
result.Path = "."
}
content, err := g.getContentRecursively(ctx, result.Path, result.Owner, result.Repository)
if err != nil {
return types.ActionResult{}, err
}
return types.ActionResult{Result: content}, nil
}
func (g *GithubRepositoryGetAllContent) Definition() types.ActionDefinition {
actionName := "get_all_github_repository_content"
if g.customActionName != "" {
actionName = g.customActionName
}
description := "Get all content of a GitHub repository recursively"
if g.repository != "" && g.owner != "" {
return types.ActionDefinition{
Name: types.ActionDefinitionName(actionName),
Description: description,
Properties: map[string]jsonschema.Definition{
"path": {
Type: jsonschema.String,
Description: "Optional path to start from (defaults to repository root)",
},
},
}
}
return types.ActionDefinition{
Name: types.ActionDefinitionName(actionName),
Description: description,
Properties: map[string]jsonschema.Definition{
"path": {
Type: jsonschema.String,
Description: "Optional path to start from (defaults to repository root)",
},
"repository": {
Type: jsonschema.String,
Description: "The repository to get content from",
},
"owner": {
Type: jsonschema.String,
Description: "The owner of the repository",
},
},
Required: []string{"repository", "owner"},
}
}
func (a *GithubRepositoryGetAllContent) Plannable() bool {
return true
}
// GithubRepositoryGetAllContentConfigMeta returns the metadata for GitHub Repository Get All Content action configuration fields
func GithubRepositoryGetAllContentConfigMeta() []config.Field {
return []config.Field{
{
Name: "token",
Label: "GitHub Token",
Type: config.FieldTypeText,
Required: true,
HelpText: "GitHub API token with repository access",
},
{
Name: "repository",
Label: "Repository",
Type: config.FieldTypeText,
Required: false,
HelpText: "GitHub repository name",
},
{
Name: "owner",
Label: "Owner",
Type: config.FieldTypeText,
Required: false,
HelpText: "GitHub repository owner",
},
{
Name: "customActionName",
Label: "Custom Action Name",
Type: config.FieldTypeText,
HelpText: "Custom name for this action",
},
}
}