From 62ce629bf1526b500ed6399adb7970feb429cf26 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 1 May 2025 22:07:41 +0200 Subject: [PATCH] feat(github): add issue editor (#106) * feat(github): add issue editor Signed-off-by: mudler * Small changes --------- Signed-off-by: mudler --- services/actions.go | 9 ++ services/actions/githubissueedit.go | 141 ++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 services/actions/githubissueedit.go diff --git a/services/actions.go b/services/actions.go index a858cde..bcae181 100644 --- a/services/actions.go +++ b/services/actions.go @@ -22,6 +22,7 @@ const ( ActionDeepResearchRunner = "deep-research-runner" ActionGithubIssueLabeler = "github-issue-labeler" ActionGithubIssueOpener = "github-issue-opener" + ActionGithubIssueEditor = "github-issue-editor" ActionGithubIssueCloser = "github-issue-closer" ActionGithubIssueSearcher = "github-issue-searcher" ActionGithubRepositoryGet = "github-repository-get-content" @@ -50,6 +51,7 @@ var AvailableActions = []string{ ActionCustom, ActionGithubIssueLabeler, ActionGithubIssueOpener, + ActionGithubIssueEditor, ActionGithubIssueCloser, ActionGithubIssueSearcher, ActionGithubRepositoryGet, @@ -117,6 +119,8 @@ func Action(name, agentName string, config map[string]string, pool *state.AgentP a = actions.NewGithubIssueLabeler(config) case ActionGithubIssueOpener: a = actions.NewGithubIssueOpener(config) + case ActionGithubIssueEditor: + a = actions.NewGithubIssueEditor(config) case ActionGithubIssueCloser: a = actions.NewGithubIssueCloser(config) case ActionGithubIssueSearcher: @@ -205,6 +209,11 @@ func ActionsConfigMeta() []config.FieldGroup { Label: "GitHub Issue Opener", Fields: actions.GithubIssueOpenerConfigMeta(), }, + { + Name: "github-issue-editor", + Label: "GitHub Issue Editor", + Fields: actions.GithubIssueEditorConfigMeta(), + }, { Name: "github-issue-closer", Label: "GitHub Issue Closer", diff --git a/services/actions/githubissueedit.go b/services/actions/githubissueedit.go new file mode 100644 index 0000000..70d1587 --- /dev/null +++ b/services/actions/githubissueedit.go @@ -0,0 +1,141 @@ +package actions + +import ( + "context" + "fmt" + + "github.com/google/go-github/v69/github" + "github.com/mudler/LocalAGI/core/types" + "github.com/mudler/LocalAGI/pkg/config" + "github.com/sashabaranov/go-openai/jsonschema" +) + +type GithubIssueEditor struct { + token, repository, owner, customActionName string + client *github.Client +} + +func NewGithubIssueEditor(config map[string]string) *GithubIssueEditor { + client := github.NewClient(nil).WithAuthToken(config["token"]) + + return &GithubIssueEditor{ + client: client, + token: config["token"], + customActionName: config["customActionName"], + repository: config["repository"], + owner: config["owner"], + } +} + +func (g *GithubIssueEditor) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) { + result := struct { + Repository string `json:"repository"` + Owner string `json:"owner"` + Description string `json:"description"` + IssueNumber int `json:"issue_number"` + }{} + err := params.Unmarshal(&result) + if err != nil { + return types.ActionResult{}, err + } + + if g.repository != "" && g.owner != "" { + result.Repository = g.repository + result.Owner = g.owner + } + + _, _, err = g.client.Issues.Edit(ctx, result.Owner, result.Repository, result.IssueNumber, + &github.IssueRequest{ + Body: &result.Description, + }) + resultString := fmt.Sprintf("Updated description for issue %d in repository %s/%s", result.IssueNumber, result.Owner, result.Repository) + if err != nil { + resultString = fmt.Sprintf("Error updating description for issue %d in repository %s/%s: %v", result.IssueNumber, result.Owner, result.Repository, err) + } + return types.ActionResult{Result: resultString}, err +} + +func (g *GithubIssueEditor) Definition() types.ActionDefinition { + actionName := "edit_github_issue" + if g.customActionName != "" { + actionName = g.customActionName + } + description := "Edit the description of a Github issue in a repository." + if g.repository != "" && g.owner != "" { + return types.ActionDefinition{ + Name: types.ActionDefinitionName(actionName), + Description: description, + Properties: map[string]jsonschema.Definition{ + "issue_number": { + Type: jsonschema.Number, + Description: "The number of the issue to edit.", + }, + "description": { + Type: jsonschema.String, + Description: "The new description for the issue.", + }, + }, + Required: []string{"issue_number", "description"}, + } + } + return types.ActionDefinition{ + Name: types.ActionDefinitionName(actionName), + Description: description, + Properties: map[string]jsonschema.Definition{ + "issue_number": { + Type: jsonschema.Number, + Description: "The number of the issue to edit.", + }, + "repository": { + Type: jsonschema.String, + Description: "The repository containing the issue.", + }, + "owner": { + Type: jsonschema.String, + Description: "The owner of the repository.", + }, + "description": { + Type: jsonschema.String, + Description: "The new description for the issue.", + }, + }, + Required: []string{"issue_number", "repository", "owner", "description"}, + } +} + +func (a *GithubIssueEditor) Plannable() bool { + return true +} + +// GithubIssueEditorConfigMeta returns the metadata for GitHub Issue Editor action configuration fields +func GithubIssueEditorConfigMeta() []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", + }, + } +}