mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 16:00:52 +00:00
216 lines
5.7 KiB
Go
216 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
)
|
|
|
|
type hermesSkillsListEntry struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
}
|
|
|
|
type hermesSkillsListPayload struct {
|
|
Skills []hermesSkillsListEntry `json:"skills"`
|
|
}
|
|
|
|
func listHermesSkills(containerName string) ([]dto.AgentSkillItem, error) {
|
|
output, err := runHermesSkillsCommandWithStdout(2*time.Minute, containerName, "list", "--source", "all")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items, err := parseHermesSkillsListOutput(output)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
metadata, err := readHermesSkillsListMetadata(containerName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mergeHermesSkillsWithMetadata(items, metadata), nil
|
|
}
|
|
|
|
func searchHermesSkills(containerName, source, keyword string) ([]dto.AgentSkillSearchItem, error) {
|
|
if source != "official" && source != "skills-sh" {
|
|
return nil, fmt.Errorf("unsupported hermes skill source: %s", source)
|
|
}
|
|
output, err := runHermesSkillsCommandWithStdout(
|
|
2*time.Minute,
|
|
containerName,
|
|
"search",
|
|
keyword,
|
|
"--source",
|
|
source,
|
|
"--limit",
|
|
"20",
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return parseHermesSkillSearchOutput(output)
|
|
}
|
|
|
|
func runHermesSkillsCommandWithStdout(timeout time.Duration, containerName string, hermesArgs ...string) (string, error) {
|
|
args := []string{"exec", "-e", "COLUMNS=240", "-u", "hermes", containerName, "hermes", "skills"}
|
|
args = append(args, hermesArgs...)
|
|
return cmd.NewCommandMgr(cmd.WithTimeout(timeout)).RunWithStdout("docker", args...)
|
|
}
|
|
|
|
func readHermesSkillsListMetadata(containerName string) (map[string]hermesSkillsListEntry, error) {
|
|
output, err := cmd.NewCommandMgr(cmd.WithTimeout(2*time.Minute)).RunWithStdout(
|
|
"docker",
|
|
"exec",
|
|
"-u",
|
|
"hermes",
|
|
containerName,
|
|
"python",
|
|
"-c",
|
|
"import sys; sys.path.insert(0, '/opt/hermes'); from tools.skills_tool import skills_list; print(skills_list())",
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return parseHermesSkillsListMetadataOutput(output)
|
|
}
|
|
|
|
func parseHermesSkillsListOutput(output string) ([]dto.AgentSkillItem, error) {
|
|
headers, rows, err := parseHermesTableOutput(output)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]dto.AgentSkillItem, 0, len(rows))
|
|
for _, row := range rows {
|
|
item := dto.AgentSkillItem{
|
|
Name: row[headers["Name"]],
|
|
Category: row[headers["Category"]],
|
|
Source: row[headers["Source"]],
|
|
Trust: row[headers["Trust"]],
|
|
Uninstallable: row[headers["Source"]] != "builtin" && row[headers["Source"]] != "local",
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func parseHermesSkillsListMetadataOutput(output string) (map[string]hermesSkillsListEntry, error) {
|
|
if strings.TrimSpace(output) == "" {
|
|
return map[string]hermesSkillsListEntry{}, nil
|
|
}
|
|
var payload hermesSkillsListPayload
|
|
if err := json.Unmarshal([]byte(output), &payload); err != nil {
|
|
return nil, err
|
|
}
|
|
metadata := make(map[string]hermesSkillsListEntry, len(payload.Skills))
|
|
for _, skill := range payload.Skills {
|
|
if skill.Name == "" {
|
|
continue
|
|
}
|
|
metadata[skill.Name] = skill
|
|
}
|
|
return metadata, nil
|
|
}
|
|
|
|
func mergeHermesSkillsWithMetadata(items []dto.AgentSkillItem, metadata map[string]hermesSkillsListEntry) []dto.AgentSkillItem {
|
|
for i := range items {
|
|
entry, ok := metadata[items[i].Name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
items[i].Description = entry.Description
|
|
if items[i].Category == "" && entry.Category != "" {
|
|
items[i].Category = entry.Category
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
func parseHermesSkillSearchOutput(output string) ([]dto.AgentSkillSearchItem, error) {
|
|
if strings.Contains(output, "No skills found matching your query.") {
|
|
return []dto.AgentSkillSearchItem{}, nil
|
|
}
|
|
headers, rows, err := parseHermesTableOutput(output)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]dto.AgentSkillSearchItem, 0, len(rows))
|
|
for _, row := range rows {
|
|
identifier := row[headers["Identifier"]]
|
|
items = append(items, dto.AgentSkillSearchItem{
|
|
Slug: identifier,
|
|
Identifier: identifier,
|
|
Name: row[headers["Name"]],
|
|
Description: row[headers["Description"]],
|
|
Source: row[headers["Source"]],
|
|
Trust: row[headers["Trust"]],
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func parseHermesTableOutput(output string) (map[string]int, [][]string, error) {
|
|
lines := strings.Split(strings.TrimSpace(ansiEscapePattern.ReplaceAllString(output, "")), "\n")
|
|
var headers []string
|
|
rows := make([][]string, 0)
|
|
var current []string
|
|
|
|
for _, rawLine := range lines {
|
|
line := strings.TrimSpace(rawLine)
|
|
if (!strings.HasPrefix(line, "│") || !strings.HasSuffix(line, "│")) &&
|
|
(!strings.HasPrefix(line, "┃") || !strings.HasSuffix(line, "┃")) {
|
|
continue
|
|
}
|
|
line = strings.ReplaceAll(line, "┃", "│")
|
|
parts := strings.Split(line, "│")
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
cols := make([]string, 0, len(parts)-2)
|
|
for _, part := range parts[1 : len(parts)-1] {
|
|
cols = append(cols, strings.TrimSpace(part))
|
|
}
|
|
if len(headers) == 0 {
|
|
headers = cols
|
|
continue
|
|
}
|
|
if cols[0] != "" {
|
|
if current != nil {
|
|
rows = append(rows, current)
|
|
}
|
|
current = cols
|
|
continue
|
|
}
|
|
if current == nil {
|
|
continue
|
|
}
|
|
for i := range cols {
|
|
if cols[i] == "" {
|
|
continue
|
|
}
|
|
if current[i] == "" {
|
|
current[i] = cols[i]
|
|
continue
|
|
}
|
|
current[i] = current[i] + " " + cols[i]
|
|
}
|
|
}
|
|
|
|
if current != nil {
|
|
rows = append(rows, current)
|
|
}
|
|
if len(headers) == 0 {
|
|
return nil, nil, fmt.Errorf("hermes skills table not found")
|
|
}
|
|
|
|
headerIndex := make(map[string]int, len(headers))
|
|
for i, header := range headers {
|
|
headerIndex[header] = i
|
|
}
|
|
return headerIndex, rows, nil
|
|
}
|