mirror of
https://github.com/mudler/LocalAGI.git
synced 2026-07-23 18:55:52 -04:00
25ce5475de
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
547 lines
15 KiB
Go
547 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/xlog"
|
|
|
|
"github.com/mudler/LocalAGI/services/actions"
|
|
)
|
|
|
|
const (
|
|
// Actions
|
|
ActionSearch = "search"
|
|
ActionCustom = "custom"
|
|
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"
|
|
ActionGenerateSong = "generate_song"
|
|
ActionGeneratePDF = "generate_pdf"
|
|
ActionCounter = "counter"
|
|
ActionCallAgents = "call_agents"
|
|
ActionShellcommand = "shell-command"
|
|
ActionSendTelegramMessage = "send-telegram-message"
|
|
ActionSetReminder = "set_reminder"
|
|
ActionSetRecurringReminder = "set_recurring_reminder"
|
|
ActionSetOneTimeReminder = "set_onetime_reminder"
|
|
ActionListReminders = "list_reminders"
|
|
ActionRemoveReminder = "remove_reminder"
|
|
ActionAddToMemory = "add_to_memory"
|
|
ActionListMemory = "list_memory"
|
|
ActionRemoveFromMemory = "remove_from_memory"
|
|
ActionSearchMemory = "search_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,
|
|
ActionGithubRepositoryCreateOrUpdate,
|
|
ActionGithubIssueReader,
|
|
ActionGithubIssueCommenter,
|
|
ActionGithubPRReader,
|
|
ActionGithubPRCommenter,
|
|
ActionGithubPRReviewer,
|
|
ActionGithubPRCreator,
|
|
ActionGithubREADME,
|
|
ActionScraper,
|
|
ActionBrowse,
|
|
ActionWikipedia,
|
|
ActionSendMail,
|
|
ActionGenerateImage,
|
|
ActionGenerateSong,
|
|
ActionGeneratePDF,
|
|
ActionTwitterPost,
|
|
ActionCounter,
|
|
ActionCallAgents,
|
|
ActionShellcommand,
|
|
ActionSendTelegramMessage,
|
|
ActionSetReminder,
|
|
ActionListReminders,
|
|
ActionRemoveReminder,
|
|
ActionAddToMemory,
|
|
ActionListMemory,
|
|
ActionRemoveFromMemory,
|
|
ActionSearchMemory,
|
|
ActionPiKVMPowerControl,
|
|
ActionWebhook,
|
|
}
|
|
|
|
var DefaultActions = []config.FieldGroup{
|
|
{
|
|
Name: "search",
|
|
Label: "Search",
|
|
Fields: actions.SearchConfigMeta(),
|
|
},
|
|
{
|
|
Name: "generate_image",
|
|
Label: "Generate Image",
|
|
Fields: actions.GenImageConfigMeta(),
|
|
},
|
|
{
|
|
Name: "generate_song",
|
|
Label: "Generate Song",
|
|
Fields: actions.GenSongConfigMeta(),
|
|
},
|
|
{
|
|
Name: "generate_pdf",
|
|
Label: "Generate PDF",
|
|
Fields: actions.GenPDFConfigMeta(),
|
|
},
|
|
{
|
|
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: "search_memory",
|
|
Label: "Search Memory",
|
|
Fields: actions.SearchMemoryConfigMeta(),
|
|
},
|
|
{
|
|
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_recurring_reminder",
|
|
Label: "Set Recurring Reminder",
|
|
Fields: []config.Field{},
|
|
},
|
|
{
|
|
Name: "set_onetime_reminder",
|
|
Label: "Set One-Time 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 (
|
|
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{}
|
|
}
|
|
|
|
memoryIdxPath := memoryIndexPath(agentName, actionsConfigs)
|
|
|
|
switch name {
|
|
case ActionCustom:
|
|
a, err = action.NewCustom(config, "")
|
|
case ActionGenerateImage:
|
|
a = actions.NewGenImage(config)
|
|
case ActionGenerateSong:
|
|
a = actions.NewGenSong(config)
|
|
case ActionGeneratePDF:
|
|
a = actions.NewGenPDF(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 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 ActionSetRecurringReminder:
|
|
a = action.NewRecurringReminder()
|
|
case ActionSetOneTimeReminder:
|
|
a = action.NewOneTimeReminder()
|
|
case ActionListReminders:
|
|
a = action.NewListReminders()
|
|
case ActionRemoveReminder:
|
|
a = action.NewRemoveReminder()
|
|
case ActionAddToMemory:
|
|
a, _, _, _ = actions.NewMemoryActions(memoryIdxPath, config)
|
|
case ActionListMemory:
|
|
_, a, _, _ = actions.NewMemoryActions(memoryIdxPath, config)
|
|
case ActionRemoveFromMemory:
|
|
_, _, a, _ = actions.NewMemoryActions(memoryIdxPath, config)
|
|
case ActionSearchMemory:
|
|
_, _, _, a = actions.NewMemoryActions(memoryIdxPath, 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
|
|
}
|