mirror of
https://github.com/mudler/LocalAGI.git
synced 2026-07-24 11:15:22 -04:00
d456315f19
* Add Webhook action handling Integrates a new "webhook" action, enabling configurable HTTP requests with customizable methods, content types, and payloads through runtime parameters. * Enhance Webhook action documentation and runtime handling Improves comments and function-level documentation for the Webhook action, clarifying its configuration, runtime parameter handling, and operational behavior. Adds detailed explanations for methods, payload templates, and HTTP request construction.
531 lines
15 KiB
Go
531 lines
15 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/mudler/LocalAGI/core/action"
|
|
"github.com/mudler/LocalAGI/core/state"
|
|
"github.com/mudler/LocalAGI/core/types"
|
|
"github.com/mudler/LocalAGI/pkg/config"
|
|
"github.com/mudler/LocalAGI/pkg/xlog"
|
|
|
|
"github.com/mudler/LocalAGI/services/actions"
|
|
)
|
|
|
|
const (
|
|
// Actions
|
|
ActionSearch = "search"
|
|
ActionCustom = "custom"
|
|
ActionBrowserAgentRunner = "browser-agent-runner"
|
|
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"
|
|
ActionGithubRepositoryCreateOrUpdate = "github-repository-create-or-update-content"
|
|
ActionGithubIssueReader = "github-issue-reader"
|
|
ActionGithubIssueCommenter = "github-issue-commenter"
|
|
ActionGithubPRReader = "github-pr-reader"
|
|
ActionGithubPRCommenter = "github-pr-commenter"
|
|
ActionGithubPRReviewer = "github-pr-reviewer"
|
|
ActionGithubPRCreator = "github-pr-creator"
|
|
ActionGithubGetAllContent = "github-get-all-repository-content"
|
|
ActionGithubREADME = "github-readme"
|
|
ActionGithubRepositorySearchFiles = "github-repository-search-files"
|
|
ActionGithubRepositoryListFiles = "github-repository-list-files"
|
|
ActionScraper = "scraper"
|
|
ActionWikipedia = "wikipedia"
|
|
ActionBrowse = "browse"
|
|
ActionTwitterPost = "twitter-post"
|
|
ActionSendMail = "send-mail"
|
|
ActionGenerateImage = "generate_image"
|
|
ActionCounter = "counter"
|
|
ActionCallAgents = "call_agents"
|
|
ActionShellcommand = "shell-command"
|
|
ActionSendTelegramMessage = "send-telegram-message"
|
|
ActionSetReminder = "set_reminder"
|
|
ActionListReminders = "list_reminders"
|
|
ActionRemoveReminder = "remove_reminder"
|
|
ActionAddToMemory = "add_to_memory"
|
|
ActionListMemory = "list_memory"
|
|
ActionRemoveFromMemory = "remove_from_memory"
|
|
ActionPiKVMPowerControl = "pikvm_power_control"
|
|
ActionWebhook = "webhook"
|
|
)
|
|
|
|
const (
|
|
nameField = "name"
|
|
descriptionField = "description"
|
|
configurationField = "configuration"
|
|
)
|
|
|
|
var AvailableActions = []string{
|
|
ActionSearch,
|
|
ActionCustom,
|
|
ActionGithubIssueLabeler,
|
|
ActionGithubIssueOpener,
|
|
ActionGithubIssueEditor,
|
|
ActionGithubIssueCloser,
|
|
ActionGithubIssueSearcher,
|
|
ActionGithubRepositoryGet,
|
|
ActionGithubGetAllContent,
|
|
ActionGithubRepositorySearchFiles,
|
|
ActionGithubRepositoryListFiles,
|
|
ActionBrowserAgentRunner,
|
|
ActionDeepResearchRunner,
|
|
ActionGithubRepositoryCreateOrUpdate,
|
|
ActionGithubIssueReader,
|
|
ActionGithubIssueCommenter,
|
|
ActionGithubPRReader,
|
|
ActionGithubPRCommenter,
|
|
ActionGithubPRReviewer,
|
|
ActionGithubPRCreator,
|
|
ActionGithubREADME,
|
|
ActionScraper,
|
|
ActionBrowse,
|
|
ActionWikipedia,
|
|
ActionSendMail,
|
|
ActionGenerateImage,
|
|
ActionTwitterPost,
|
|
ActionCounter,
|
|
ActionCallAgents,
|
|
ActionShellcommand,
|
|
ActionSendTelegramMessage,
|
|
ActionSetReminder,
|
|
ActionListReminders,
|
|
ActionRemoveReminder,
|
|
ActionAddToMemory,
|
|
ActionListMemory,
|
|
ActionRemoveFromMemory,
|
|
ActionPiKVMPowerControl,
|
|
ActionWebhook,
|
|
}
|
|
|
|
var DefaultActions = []config.FieldGroup{
|
|
{
|
|
Name: "search",
|
|
Label: "Search",
|
|
Fields: actions.SearchConfigMeta(),
|
|
},
|
|
{
|
|
Name: "browser-agent-runner",
|
|
Label: "Browser Agent Runner",
|
|
Fields: actions.BrowserAgentRunnerConfigMeta(),
|
|
},
|
|
{
|
|
Name: "deep-research-runner",
|
|
Label: "Deep Research Runner",
|
|
Fields: actions.DeepResearchRunnerConfigMeta(),
|
|
},
|
|
{
|
|
Name: "generate_image",
|
|
Label: "Generate Image",
|
|
Fields: actions.GenImageConfigMeta(),
|
|
},
|
|
{
|
|
Name: "add_to_memory",
|
|
Label: "Add to Memory",
|
|
Fields: actions.AddToMemoryConfigMeta(),
|
|
},
|
|
{
|
|
Name: "list_memory",
|
|
Label: "List Memory",
|
|
Fields: actions.ListMemoryConfigMeta(),
|
|
},
|
|
{
|
|
Name: "remove_from_memory",
|
|
Label: "Remove from Memory",
|
|
Fields: actions.RemoveFromMemoryConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-issue-labeler",
|
|
Label: "GitHub Issue Labeler",
|
|
Fields: actions.GithubIssueLabelerConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-issue-opener",
|
|
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",
|
|
Fields: actions.GithubIssueCloserConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-issue-commenter",
|
|
Label: "GitHub Issue Commenter",
|
|
Fields: actions.GithubIssueCommenterConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-issue-reader",
|
|
Label: "GitHub Issue Reader",
|
|
Fields: actions.GithubIssueReaderConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-issue-searcher",
|
|
Label: "GitHub Issue Search",
|
|
Fields: actions.GithubIssueSearchConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-repository-get-content",
|
|
Label: "GitHub Repository Get Content",
|
|
Fields: actions.GithubRepositoryGetContentConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-get-all-repository-content",
|
|
Label: "GitHub Get All Repository Content",
|
|
Fields: actions.GithubRepositoryGetAllContentConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-repository-search-files",
|
|
Label: "GitHub Repository Search Files",
|
|
Fields: actions.GithubRepositorySearchFilesConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-repository-list-files",
|
|
Label: "GitHub Repository List Files",
|
|
Fields: actions.GithubRepositoryListFilesConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-repository-create-or-update-content",
|
|
Label: "GitHub Repository Create/Update Content",
|
|
Fields: actions.GithubRepositoryCreateOrUpdateContentConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-readme",
|
|
Label: "GitHub Repository README",
|
|
Fields: actions.GithubRepositoryREADMEConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-pr-reader",
|
|
Label: "GitHub PR Reader",
|
|
Fields: actions.GithubPRReaderConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-pr-commenter",
|
|
Label: "GitHub PR Commenter",
|
|
Fields: actions.GithubPRCommenterConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-pr-reviewer",
|
|
Label: "GitHub PR Reviewer",
|
|
Fields: actions.GithubPRReviewerConfigMeta(),
|
|
},
|
|
{
|
|
Name: "github-pr-creator",
|
|
Label: "GitHub PR Creator",
|
|
Fields: actions.GithubPRCreatorConfigMeta(),
|
|
},
|
|
{
|
|
Name: "twitter-post",
|
|
Label: "Twitter Post",
|
|
Fields: actions.TwitterPostConfigMeta(),
|
|
},
|
|
{
|
|
Name: "send-mail",
|
|
Label: "Send Mail",
|
|
Fields: actions.SendMailConfigMeta(),
|
|
},
|
|
{
|
|
Name: "shell-command",
|
|
Label: "Shell Command",
|
|
Fields: actions.ShellConfigMeta(),
|
|
},
|
|
{
|
|
Name: "custom",
|
|
Label: "Custom",
|
|
Fields: action.CustomConfigMeta(),
|
|
},
|
|
{
|
|
Name: "scraper",
|
|
Label: "Scraper",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "wikipedia",
|
|
Label: "Wikipedia",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "browse",
|
|
Label: "Browse",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "counter",
|
|
Label: "Counter",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "call_agents",
|
|
Label: "Call Agents",
|
|
Fields: actions.CallAgentConfigMeta(),
|
|
},
|
|
{
|
|
Name: "send-telegram-message",
|
|
Label: "Send Telegram Message",
|
|
Fields: actions.SendTelegramMessageConfigMeta(),
|
|
},
|
|
{
|
|
Name: "set_reminder",
|
|
Label: "Set Reminder",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "list_reminders",
|
|
Label: "List Reminders",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "remove_reminder",
|
|
Label: "Remove Reminder",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "pikvm_power_control",
|
|
Label: "PiKVM Power Control",
|
|
Fields: actions.PiKVMConfigMeta(),
|
|
},
|
|
{
|
|
Name: "webhook",
|
|
Label: "Webhook",
|
|
Fields: actions.WebhookConfigMeta(),
|
|
},
|
|
}
|
|
|
|
const (
|
|
ActionConfigBrowserAgentRunner = "browser-agent-runner-base-url"
|
|
ActionConfigDeepResearchRunner = "deep-research-runner-base-url"
|
|
ActionConfigSSHBoxURL = "sshbox-url"
|
|
ConfigStateDir = "state-dir"
|
|
CustomActionsDir = "custom-actions-dir"
|
|
)
|
|
|
|
func customActions(customActionsDir string, existingActionConfigs map[string]map[string]string) (allActions []types.Action) {
|
|
files, err := os.ReadDir(customActionsDir)
|
|
if err != nil {
|
|
xlog.Error("Error reading custom actions directory", "error", err)
|
|
return allActions
|
|
}
|
|
|
|
for _, file := range files {
|
|
if filepath.Ext(file.Name()) != ".go" {
|
|
continue
|
|
}
|
|
|
|
content, err := os.ReadFile(filepath.Join(customActionsDir, file.Name()))
|
|
if err != nil {
|
|
xlog.Error("Error reading custom action file", "error", err, "file", file.Name())
|
|
continue
|
|
}
|
|
actionName := strings.TrimSuffix(file.Name(), ".go")
|
|
|
|
actionConfig := map[string]string{
|
|
"name": actionName,
|
|
"description": "",
|
|
"code": string(content),
|
|
"unsafe": "false",
|
|
}
|
|
|
|
if c, exists := existingActionConfigs[actionName]; exists {
|
|
// We allow the user to customize name and description
|
|
actionConfig[descriptionField] = c[descriptionField]
|
|
actionConfig[nameField] = c[nameField]
|
|
actionConfig[configurationField] = c[configurationField]
|
|
}
|
|
a, err := Action(ActionCustom, "", actionConfig, nil, map[string]string{})
|
|
if err != nil {
|
|
xlog.Error("Error creating custom action", "error", err, "file", file.Name())
|
|
continue
|
|
}
|
|
allActions = append(allActions, a)
|
|
}
|
|
return
|
|
}
|
|
|
|
func Actions(actionsConfigs map[string]string) func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
|
return func(a *state.AgentConfig) func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
|
return func(ctx context.Context, pool *state.AgentPool) []types.Action {
|
|
allActions := []types.Action{}
|
|
|
|
agentName := a.Name
|
|
|
|
existingActionConfigs := map[string]map[string]string{}
|
|
for _, a := range a.Actions {
|
|
var config map[string]string
|
|
if err := json.Unmarshal([]byte(a.Config), &config); err != nil {
|
|
xlog.Error("Error unmarshalling action config", "error", err)
|
|
continue
|
|
}
|
|
|
|
existingActionConfigs[a.Name] = config
|
|
|
|
a, err := Action(a.Name, agentName, config, pool, actionsConfigs)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
allActions = append(allActions, a)
|
|
}
|
|
|
|
// Now we will scan a directory for custom actions
|
|
if actionsConfigs[CustomActionsDir] != "" {
|
|
allActions = append(allActions, customActions(actionsConfigs[CustomActionsDir], existingActionConfigs)...)
|
|
}
|
|
|
|
return allActions
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func Action(name, agentName string, config map[string]string, pool *state.AgentPool, actionsConfigs map[string]string) (types.Action, error) {
|
|
var a types.Action
|
|
var err error
|
|
|
|
if config == nil {
|
|
config = map[string]string{}
|
|
}
|
|
|
|
memoryFilePath := memoryPath(agentName, actionsConfigs)
|
|
|
|
switch name {
|
|
case ActionCustom:
|
|
a, err = action.NewCustom(config, "")
|
|
case ActionGenerateImage:
|
|
a = actions.NewGenImage(config)
|
|
case ActionSearch:
|
|
a = actions.NewSearch(config)
|
|
case ActionGithubIssueLabeler:
|
|
a = actions.NewGithubIssueLabeler(config)
|
|
case ActionGithubIssueOpener:
|
|
a = actions.NewGithubIssueOpener(config)
|
|
case ActionGithubIssueEditor:
|
|
a = actions.NewGithubIssueEditor(config)
|
|
case ActionGithubIssueCloser:
|
|
a = actions.NewGithubIssueCloser(config)
|
|
case ActionGithubIssueSearcher:
|
|
a = actions.NewGithubIssueSearch(config)
|
|
case ActionBrowserAgentRunner:
|
|
a = actions.NewBrowserAgentRunner(config, actionsConfigs[ActionConfigBrowserAgentRunner])
|
|
case ActionDeepResearchRunner:
|
|
a = actions.NewDeepResearchRunner(config, actionsConfigs[ActionConfigDeepResearchRunner])
|
|
case ActionGithubIssueReader:
|
|
a = actions.NewGithubIssueReader(config)
|
|
case ActionGithubPRReader:
|
|
a = actions.NewGithubPRReader(config)
|
|
case ActionGithubPRCommenter:
|
|
a = actions.NewGithubPRCommenter(config)
|
|
case ActionGithubPRReviewer:
|
|
a = actions.NewGithubPRReviewer(config)
|
|
case ActionGithubPRCreator:
|
|
a = actions.NewGithubPRCreator(config)
|
|
case ActionGithubGetAllContent:
|
|
a = actions.NewGithubRepositoryGetAllContent(config)
|
|
case ActionGithubRepositorySearchFiles:
|
|
a = actions.NewGithubRepositorySearchFiles(config)
|
|
case ActionGithubRepositoryListFiles:
|
|
a = actions.NewGithubRepositoryListFiles(config)
|
|
case ActionGithubIssueCommenter:
|
|
a = actions.NewGithubIssueCommenter(config)
|
|
case ActionGithubRepositoryGet:
|
|
a = actions.NewGithubRepositoryGetContent(config)
|
|
case ActionGithubRepositoryCreateOrUpdate:
|
|
a = actions.NewGithubRepositoryCreateOrUpdateContent(config)
|
|
case ActionGithubREADME:
|
|
a = actions.NewGithubRepositoryREADME(config)
|
|
case ActionScraper:
|
|
a = actions.NewScraper(config)
|
|
case ActionWikipedia:
|
|
a = actions.NewWikipedia(config)
|
|
case ActionBrowse:
|
|
a = actions.NewBrowse(config)
|
|
case ActionSendMail:
|
|
a = actions.NewSendMail(config)
|
|
case ActionWebhook:
|
|
a = actions.NewWebhook(config)
|
|
case ActionTwitterPost:
|
|
a = actions.NewPostTweet(config)
|
|
case ActionCounter:
|
|
a = actions.NewCounter(config)
|
|
case ActionCallAgents:
|
|
a = actions.NewCallAgent(config, agentName, pool.InternalAPI())
|
|
case ActionShellcommand:
|
|
a = actions.NewShell(config, actionsConfigs[ActionConfigSSHBoxURL])
|
|
case ActionSendTelegramMessage:
|
|
a = actions.NewSendTelegramMessageRunner(config)
|
|
case ActionSetReminder:
|
|
a = action.NewReminder()
|
|
case ActionListReminders:
|
|
a = action.NewListReminders()
|
|
case ActionRemoveReminder:
|
|
a = action.NewRemoveReminder()
|
|
case ActionAddToMemory:
|
|
a, _, _ = actions.NewMemoryActions(memoryFilePath, config)
|
|
case ActionListMemory:
|
|
_, a, _ = actions.NewMemoryActions(memoryFilePath, config)
|
|
case ActionRemoveFromMemory:
|
|
_, _, a = actions.NewMemoryActions(memoryFilePath, config)
|
|
case ActionPiKVMPowerControl:
|
|
a = actions.NewPiKVMAction(config)
|
|
default:
|
|
xlog.Error("Action not found", "name", name)
|
|
return nil, fmt.Errorf("Action not found")
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
func ActionsConfigMeta(customActionDir string) []config.FieldGroup {
|
|
all := slices.Clone(DefaultActions)
|
|
|
|
if customActionDir != "" {
|
|
actions := customActions(customActionDir, map[string]map[string]string{})
|
|
|
|
for _, a := range actions {
|
|
all = append(all, config.FieldGroup{
|
|
Name: a.Definition().Name.String(),
|
|
Label: a.Definition().Name.String(),
|
|
Fields: []config.Field{
|
|
{
|
|
Name: nameField,
|
|
Label: "Name",
|
|
Type: config.FieldTypeText,
|
|
HelpText: "Name of the custom action",
|
|
},
|
|
{
|
|
Name: descriptionField,
|
|
Label: "Description",
|
|
Type: config.FieldTypeTextarea,
|
|
HelpText: "Description of the custom action",
|
|
},
|
|
{
|
|
Name: configurationField,
|
|
Label: "Configuration",
|
|
Type: config.FieldTypeTextarea,
|
|
HelpText: "Configuration of the custom action",
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|
|
return all
|
|
}
|