Browse Source

feat: cli

Yeuoly 11 months ago
parent
commit
9253da2c67

+ 72 - 0
cmd/commandline/language.go

@@ -0,0 +1,72 @@
+package main
+
+import (
+	"fmt"
+
+	tea "github.com/charmbracelet/bubbletea"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/constants"
+)
+
+var languages = []constants.Language{
+	constants.Python,
+	constants.Go + " (not supported yet)",
+}
+
+type language struct {
+	cursor int
+}
+
+func newLanguage() language {
+	return language{
+		// default language is python
+		cursor: 0,
+	}
+}
+
+func (l language) Language() constants.Language {
+	return languages[l.cursor]
+}
+
+func (l language) View() string {
+	s := "Select the language you want to use for plugin development\n"
+	for i, language := range languages {
+		if i == l.cursor {
+			s += fmt.Sprintf("-> %s\n", language)
+		} else {
+			s += fmt.Sprintf("  %s\n", language)
+		}
+	}
+	return s
+}
+
+func (l language) Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.String() {
+		case "ctrl+c", "q":
+			return l, SUB_MENU_EVENT_NONE, tea.Quit
+		case "j", "down":
+			l.cursor++
+			if l.cursor >= len(languages) {
+				l.cursor = len(languages) - 1
+			}
+		case "k", "up":
+			l.cursor--
+			if l.cursor < 0 {
+				l.cursor = 0
+			}
+		case "enter":
+			if l.cursor != 0 {
+				return l, SUB_MENU_EVENT_NONE, nil
+			}
+
+			return l, SUB_MENU_EVENT_NEXT, nil
+		}
+	}
+
+	return l, SUB_MENU_EVENT_NONE, nil
+}
+
+func (l language) Init() tea.Cmd {
+	return nil
+}

+ 84 - 0
cmd/commandline/main.go

@@ -0,0 +1,84 @@
+package main
+
+import (
+	"fmt"
+
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+type subMenuKey string
+
+const (
+	SUB_MENU_KEY_PROFILE    subMenuKey = "profile"
+	SUB_MENU_KEY_LANGUAGE   subMenuKey = "language"
+	SUB_MENU_KEY_PERMISSION subMenuKey = "permission"
+)
+
+type model struct {
+	subMenus       map[subMenuKey]subMenu
+	subMenuSeq     []subMenuKey
+	currentSubMenu subMenuKey
+}
+
+func initialize() model {
+	m := model{}
+	m.subMenus = map[subMenuKey]subMenu{
+		SUB_MENU_KEY_PROFILE:    newProfile(),
+		SUB_MENU_KEY_LANGUAGE:   newLanguage(),
+		SUB_MENU_KEY_PERMISSION: newPermission(),
+	}
+	m.currentSubMenu = SUB_MENU_KEY_PROFILE
+
+	m.subMenuSeq = []subMenuKey{
+		SUB_MENU_KEY_PROFILE,
+		SUB_MENU_KEY_LANGUAGE,
+		SUB_MENU_KEY_PERMISSION,
+	}
+
+	return m
+}
+
+func (m model) Init() tea.Cmd {
+	return nil
+}
+
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	currentSubMenu, event, cmd := m.subMenus[m.currentSubMenu].Update(msg)
+	m.subMenus[m.currentSubMenu] = currentSubMenu
+
+	switch event {
+	case SUB_MENU_EVENT_NEXT:
+		if m.currentSubMenu != m.subMenuSeq[len(m.subMenuSeq)-1] {
+			// move the current sub menu to the next one
+			for i, key := range m.subMenuSeq {
+				if key == m.currentSubMenu {
+					m.currentSubMenu = m.subMenuSeq[i+1]
+					break
+				}
+			}
+		}
+	case SUB_MENU_EVENT_PREV:
+		if m.currentSubMenu != m.subMenuSeq[0] {
+			// move the current sub menu to the previous one
+			for i, key := range m.subMenuSeq {
+				if key == m.currentSubMenu {
+					m.currentSubMenu = m.subMenuSeq[i-1]
+					break
+				}
+			}
+		}
+	}
+
+	return m, cmd
+}
+
+func (m model) View() string {
+	return m.subMenus[m.currentSubMenu].View()
+}
+
+func main() {
+	p := tea.NewProgram(initialize())
+	if _, err := p.Run(); err != nil {
+		fmt.Println("Error running program:", err)
+	}
+}

+ 268 - 0
cmd/commandline/permission.go

@@ -0,0 +1,268 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	ti "github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+)
+
+var permissionKeySeq = []string{
+	"tool.enabled",
+	"model.enabled",
+	"model.llm",
+	"model.text_embedding",
+	"model.rerank",
+	"model.tts",
+	"model.speech2text",
+	"model.moderation",
+	"app.enabled",
+	"storage.enabled",
+	"storage.size",
+	"endpoint.enabled",
+}
+
+type permission struct {
+	cursor string
+
+	permission plugin_entities.PluginPermissionRequirement
+
+	storageSizeEditor ti.Model
+}
+
+func newPermission() permission {
+	return permission{
+		cursor:            permissionKeySeq[0],
+		storageSizeEditor: ti.New(),
+	}
+}
+
+func (p permission) View() string {
+	cursor := func(key string) string {
+		if p.cursor == key {
+			return "→ "
+		}
+		return "  "
+	}
+
+	checked := func(enabled bool) string {
+		if enabled {
+			return fmt.Sprintf("\033[32m%s\033[0m", "[✔]")
+		}
+		return fmt.Sprintf("\033[31m%s\033[0m", "[✘]")
+	}
+
+	s := "Configure the permissions of the plugin, use up and down to navigate, enter to select, after selection, press right to move to the next menu\n"
+	s += "Backwards Invocation:\n"
+	s += "Tools:\n"
+	s += fmt.Sprintf("  %sEnabled: %v\n", cursor("tool.enabled"), checked(p.permission.AllowInvokeTool()))
+	s += "Models:\n"
+	s += fmt.Sprintf("  %sEnabled: %v\n", cursor("model.enabled"), checked(p.permission.AllowInvokeModel()))
+	s += fmt.Sprintf("  %sLLM: %v\n", cursor("model.llm"), checked(p.permission.AllowInvokeLLM()))
+	s += fmt.Sprintf("  %sText Embedding: %v\n", cursor("model.text_embedding"), checked(p.permission.AllowInvokeTextEmbedding()))
+	s += fmt.Sprintf("  %sRerank: %v\n", cursor("model.rerank"), checked(p.permission.AllowInvokeRerank()))
+	s += fmt.Sprintf("  %sTTS: %v\n", cursor("model.tts"), checked(p.permission.AllowInvokeTTS()))
+	s += fmt.Sprintf("  %sSpeech2Text: %v\n", cursor("model.speech2text"), checked(p.permission.AllowInvokeSpeech2Text()))
+	s += fmt.Sprintf("  %sModeration: %v\n", cursor("model.moderation"), checked(p.permission.AllowInvokeModeration()))
+	s += "Apps:\n"
+	s += fmt.Sprintf("  %sEnabled: %v\n", cursor("app.enabled"), checked(p.permission.AllowInvokeApp()))
+	s += "Resources:\n"
+	s += "Storage:\n"
+	s += fmt.Sprintf("  %sEnabled: %v\n", cursor("storage.enabled"), checked(p.permission.AllowInvokeStorage()))
+
+	if p.permission.AllowInvokeStorage() {
+		s += fmt.Sprintf("  %sSize: %v\n", cursor("storage.size"), p.storageSizeEditor.View())
+	} else {
+		s += fmt.Sprintf("  %sSize: %v\n", cursor("storage.size"), "N/A")
+	}
+
+	s += "Endpoints:\n"
+	s += fmt.Sprintf("  %sEnabled: %v\n", cursor("endpoint.enabled"), checked(p.permission.AllowRegistryEndpoint()))
+	return s
+}
+
+func (p *permission) edit() {
+	if p.cursor == "tool.enabled" {
+		if p.permission.AllowInvokeTool() {
+			p.permission.Tool = nil
+		} else {
+			p.permission.Tool = &plugin_entities.PluginPermissionToolRequirement{
+				Enabled: true,
+			}
+		}
+	}
+
+	if strings.HasPrefix(p.cursor, "model.") {
+		if p.permission.AllowInvokeModel() {
+			if p.cursor == "model.enabled" {
+				p.permission.Model = nil
+				return
+			}
+		} else {
+			p.permission.Model = &plugin_entities.PluginPermissionModelRequirement{
+				Enabled: true,
+			}
+		}
+	}
+
+	if p.cursor == "model.llm" {
+		if p.permission.AllowInvokeLLM() {
+			p.permission.Model.LLM = false
+		} else {
+			p.permission.Model.LLM = true
+		}
+	}
+
+	if p.cursor == "model.text_embedding" {
+		if p.permission.AllowInvokeTextEmbedding() {
+			p.permission.Model.TextEmbedding = false
+		} else {
+			p.permission.Model.TextEmbedding = true
+		}
+	}
+
+	if p.cursor == "model.rerank" {
+		if p.permission.AllowInvokeRerank() {
+			p.permission.Model.Rerank = false
+		} else {
+			p.permission.Model.Rerank = true
+		}
+	}
+
+	if p.cursor == "model.tts" {
+		if p.permission.AllowInvokeTTS() {
+			p.permission.Model.TTS = false
+		} else {
+			p.permission.Model.TTS = true
+		}
+	}
+
+	if p.cursor == "model.speech2text" {
+		if p.permission.AllowInvokeSpeech2Text() {
+			p.permission.Model.Speech2text = false
+		} else {
+			p.permission.Model.Speech2text = true
+		}
+	}
+
+	if p.cursor == "model.moderation" {
+		if p.permission.AllowInvokeModeration() {
+			p.permission.Model.Moderation = false
+		} else {
+			p.permission.Model.Moderation = true
+		}
+	}
+
+	if p.cursor == "app.enabled" {
+		if p.permission.AllowInvokeApp() {
+			p.permission.App = nil
+		} else {
+			p.permission.App = &plugin_entities.PluginPermissionAppRequirement{
+				Enabled: true,
+			}
+		}
+	}
+
+	if p.cursor == "storage.enabled" {
+		if p.permission.AllowInvokeStorage() {
+			p.permission.Storage = nil
+		} else {
+			p.permission.Storage = &plugin_entities.PluginPermissionStorageRequirement{
+				Enabled: true,
+				Size:    1048576,
+			}
+		}
+	}
+
+	if p.cursor == "endpoint.enabled" {
+		if p.permission.AllowRegistryEndpoint() {
+			p.permission.Endpoint = nil
+		} else {
+			p.permission.Endpoint = &plugin_entities.PluginPermissionEndpointRequirement{
+				Enabled: true,
+			}
+		}
+	}
+}
+
+func (p *permission) updateStorageSize() {
+	if p.cursor == "storage.size" {
+		// set the storage size editor to the current storage size
+		if p.permission.AllowInvokeStorage() {
+			p.storageSizeEditor.SetValue(fmt.Sprintf("%d", p.permission.Storage.Size))
+			p.storageSizeEditor.Focus()
+		}
+	} else {
+		p.storageSizeEditor.Blur()
+		// get the storage size from the editor
+		if p.permission.AllowInvokeStorage() {
+			p.permission.Storage.Size, _ = strconv.ParseUint(p.storageSizeEditor.Value(), 10, 64)
+		}
+	}
+}
+
+func (p permission) Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd) {
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.String() {
+		case "ctrl+c":
+			return p, SUB_MENU_EVENT_NONE, tea.Quit
+		case "down":
+			// find the next key in the permissionKeySeq
+			for i, key := range permissionKeySeq {
+				if key == p.cursor {
+					if i == len(permissionKeySeq)-1 {
+						p.cursor = permissionKeySeq[0]
+					} else {
+						p.cursor = permissionKeySeq[i+1]
+					}
+
+					p.updateStorageSize()
+					break
+				}
+			}
+		case "up":
+			// find the previous key in the permissionKeySeq
+			for i, key := range permissionKeySeq {
+				if key == p.cursor {
+					if i == 0 {
+						p.cursor = permissionKeySeq[len(permissionKeySeq)-1]
+					} else {
+						p.cursor = permissionKeySeq[i-1]
+					}
+
+					p.updateStorageSize()
+					break
+				}
+			}
+		case "enter":
+			p.edit()
+		case "right":
+			if p.cursor == "storage.size" {
+				break
+			}
+			p.cursor = permissionKeySeq[0]
+			p.updateStorageSize()
+			return p, SUB_MENU_EVENT_NEXT, nil
+		}
+	}
+
+	// update storage size editor
+	if p.cursor == "storage.size" {
+		if p.storageSizeEditor.Focused() {
+			// check if msg is a number
+			model, cmd := p.storageSizeEditor.Update(msg)
+			p.storageSizeEditor = model
+			return p, SUB_MENU_EVENT_NONE, cmd
+		}
+	}
+
+	return p, SUB_MENU_EVENT_NONE, nil
+}
+
+func (p permission) Init() tea.Cmd {
+	return nil
+}

+ 97 - 0
cmd/commandline/profile.go

@@ -0,0 +1,97 @@
+package main
+
+import (
+	"fmt"
+
+	ti "github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+type profile struct {
+	cursor int
+	inputs []ti.Model
+}
+
+func newProfile() profile {
+	name := ti.New()
+	name.Placeholder = "Plugin name, a directory will be created with this name"
+	name.CharLimit = 128
+	name.Prompt = "Plugin name (press Enter to next step): "
+	name.Focus()
+
+	author := ti.New()
+	author.Placeholder = "Author name"
+	author.CharLimit = 128
+	author.Prompt = "Author (press Enter to next step): "
+
+	return profile{
+		inputs: []ti.Model{name, author},
+	}
+}
+
+func (p profile) Name() string {
+	return p.inputs[0].Value()
+}
+
+func (p profile) Author() string {
+	return p.inputs[1].Value()
+}
+
+func (p profile) View() string {
+	return fmt.Sprintf("Edit profile of the plugin\n%s\n%s\n", p.inputs[0].View(), p.inputs[1].View())
+}
+
+func (p profile) Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd) {
+	var cmds []tea.Cmd
+
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.String() {
+		case "ctrl+c", "q":
+			return p, SUB_MENU_EVENT_NONE, tea.Quit
+		case "down":
+			// focus next
+			p.cursor++
+			if p.cursor >= len(p.inputs) {
+				p.cursor = 0
+			}
+		case "up":
+			// focus previous
+			p.cursor--
+			if p.cursor < 0 {
+				p.cursor = len(p.inputs) - 1
+			}
+		case "enter":
+			// submit
+			if p.cursor == len(p.inputs)-1 {
+				return p, SUB_MENU_EVENT_NEXT, nil
+			}
+			// move to next
+			p.cursor++
+		}
+	}
+
+	// update cursor
+	for i := 0; i < len(p.inputs); i++ {
+		if i == p.cursor {
+			p.inputs[i].Focus()
+		} else {
+			p.inputs[i].Blur()
+		}
+	}
+
+	// update view
+	for i := range p.inputs {
+		var cmd tea.Cmd
+		p.inputs[i], cmd = p.inputs[i].Update(msg)
+		if cmd != nil {
+			cmds = append(cmds, cmd)
+		}
+	}
+
+	return p, SUB_MENU_EVENT_NONE, tea.Batch(cmds...)
+}
+
+func (p profile) Init() tea.Cmd {
+	return nil
+}

+ 19 - 0
cmd/commandline/submenu.go

@@ -0,0 +1,19 @@
+package main
+
+import tea "github.com/charmbracelet/bubbletea"
+
+type subMenuEvent string
+
+const (
+	SUB_MENU_EVENT_NEXT subMenuEvent = "next"
+	SUB_MENU_EVENT_PREV subMenuEvent = "prev"
+	SUB_MENU_EVENT_NONE subMenuEvent = "none"
+)
+
+type subMenu interface {
+	Init() tea.Cmd
+
+	View() string
+
+	Update(msg tea.Msg) (subMenu, subMenuEvent, tea.Cmd)
+}

+ 18 - 32
cmd/tests/main.go

@@ -1,42 +1,28 @@
 package main
 
-import "fmt"
+import (
+	"encoding/json"
+	"fmt"
 
-type Inf interface {
-	Set(int)
-}
-
-type A struct {
-	num int
-}
-
-func (a *A) Set(data int) {
-	a.num = data
-}
-
-type B struct {
-	num int
-}
-
-func (b *B) Set(data int) {
-	b.num = data
-}
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+)
 
-type C interface {
-	*A | *B
+const data = `name: John
+age: 30
+a:
+  b: 2
+`
 
-	Inf
-}
-
-type D[T C] struct {
-	data T
+type Test struct {
+	Name string `yaml:"name"`
+	Age  int    `yaml:"age"`
+	A    json.RawMessage
 }
 
 func main() {
-	d := D[*B]{
-		data: &B{},
+	ret, err := parser.UnmarshalYamlBytes[Test]([]byte(data))
+	if err != nil {
+		fmt.Println(err)
 	}
-	d.data.Set(10)
-
-	fmt.Println(d.data.num)
+	fmt.Println(ret)
 }

+ 17 - 2
go.mod

@@ -16,6 +16,7 @@ require (
 )
 
 require (
+	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect
@@ -30,14 +31,28 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect
 	github.com/aws/smithy-go v1.20.4 // indirect
+	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/charmbracelet/bubbles v0.19.0 // indirect
+	github.com/charmbracelet/bubbletea v1.1.0 // indirect
+	github.com/charmbracelet/lipgloss v0.13.0 // indirect
+	github.com/charmbracelet/x/ansi v0.2.3 // indirect
+	github.com/charmbracelet/x/term v0.2.0 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 	github.com/jackc/pgx/v5 v5.5.5 // indirect
 	github.com/jackc/puddle/v2 v2.2.1 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+	github.com/mattn/go-localereader v0.0.1 // indirect
+	github.com/mattn/go-runewidth v0.0.16 // indirect
+	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+	github.com/muesli/cancelreader v0.2.2 // indirect
+	github.com/muesli/termenv v0.15.2 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/rogpeppe/go-internal v1.12.0 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
@@ -78,8 +93,8 @@ require (
 	golang.org/x/crypto v0.24.0 // indirect
 	golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
 	golang.org/x/net v0.26.0
-	golang.org/x/sync v0.7.0 // indirect
-	golang.org/x/sys v0.21.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sys v0.24.0 // indirect
 	golang.org/x/text v0.16.0 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect

+ 38 - 0
go.sum

@@ -1,3 +1,5 @@
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
 github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8=
 github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU=
@@ -34,6 +36,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wI
 github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0=
 github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
 github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
 github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
 github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -44,6 +48,16 @@ github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3z
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0=
+github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA=
+github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
+github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
+github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
+github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
+github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
+github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
+github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
+github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
 github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
 github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
@@ -53,6 +67,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
 github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
 github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
 github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
@@ -104,8 +120,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -113,6 +137,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
+github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 github.com/panjf2000/ants v1.3.0 h1:8pQ+8leaLc9lys2viEEr8md0U4RN6uOSUCE9bOYjQ9M=
 github.com/panjf2000/ants v1.3.0/go.mod h1:AaACblRPzq35m1g3enqYcxspbbiOJJYaxU2wMpm1cXY=
 github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
@@ -125,6 +155,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
 github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
 github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@@ -170,10 +203,15 @@ golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
 golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
 golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=

+ 1 - 0
internal/types/entities/constants/language.go

@@ -9,6 +9,7 @@ type Language string
 
 const (
 	Python Language = "python"
+	Go     Language = "go" // not supported yet
 )
 
 func isAvailableLanguage(fl validator.FieldLevel) bool {

+ 4 - 0
internal/types/entities/plugin_entities/plugin_declaration.go

@@ -30,6 +30,10 @@ func (p *PluginPermissionRequirement) AllowInvokeTool() bool {
 	return p != nil && p.Tool != nil && p.Tool.Enabled
 }
 
+func (p *PluginPermissionRequirement) AllowInvokeModel() bool {
+	return p != nil && p.Model != nil && p.Model.Enabled
+}
+
 func (p *PluginPermissionRequirement) AllowInvokeLLM() bool {
 	return p != nil && p.Model != nil && p.Model.Enabled && p.Model.LLM
 }