Files
Ettore Di Giacinto 25ce5475de chore: drop deprecated set reminders
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-02-08 23:03:03 +01:00

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
}