mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 16:13:59 +08:00
695 lines
18 KiB
Go
695 lines
18 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/files"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const hermesWorkspaceDir = "/opt/data/workspace"
|
|
const hermesExecutablePath = "/opt/hermes/.venv/bin/hermes"
|
|
|
|
type hermesConfig struct {
|
|
Model hermesModelConfig `yaml:"model"`
|
|
Terminal hermesTerminalConfig `yaml:"terminal"`
|
|
Timezone string `yaml:"timezone,omitempty"`
|
|
}
|
|
|
|
type hermesModelConfig struct {
|
|
Default string `yaml:"default"`
|
|
Provider string `yaml:"provider"`
|
|
BaseURL string `yaml:"base_url,omitempty"`
|
|
APIKey string `yaml:"api_key,omitempty"`
|
|
}
|
|
|
|
type hermesTerminalConfig struct {
|
|
Backend string `yaml:"backend"`
|
|
Cwd string `yaml:"cwd"`
|
|
}
|
|
|
|
type hermesEnvEntry struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
func buildHermesDockerExecCommandArgs(containerName, command string, commandArgs ...string) []string {
|
|
args := []string{"exec", "-u", "hermes", containerName, command}
|
|
return append(args, commandArgs...)
|
|
}
|
|
|
|
func buildHermesDockerExecArgs(containerName string, hermesArgs ...string) []string {
|
|
return buildHermesDockerExecCommandArgs(containerName, "hermes", hermesArgs...)
|
|
}
|
|
|
|
func writeHermesConfig(confDir string, account *model.AgentAccount, modelName string, timezone string) error {
|
|
if strings.TrimSpace(confDir) == "" {
|
|
return fmt.Errorf("config dir is required")
|
|
}
|
|
if account == nil {
|
|
return fmt.Errorf("account is required")
|
|
}
|
|
if strings.TrimSpace(modelName) == "" {
|
|
return fmt.Errorf("model is required")
|
|
}
|
|
fileOp := files.NewFileOp()
|
|
if !fileOp.Stat(confDir) {
|
|
if err := fileOp.CreateDir(confDir, constant.DirPerm); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
provider := resolveHermesProvider(account.Provider, account.APIType)
|
|
configPath := path.Join(confDir, "config.yaml")
|
|
cfg, err := readHermesConfigMap(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
model := map[string]interface{}{
|
|
"default": resolveHermesModel(account.Provider, provider, modelName),
|
|
"provider": provider,
|
|
"base_url": account.BaseURL,
|
|
}
|
|
if provider == "custom" && account.APIKey != "" {
|
|
model["api_key"] = "${CUSTOM_API_KEY}"
|
|
}
|
|
cfg["model"] = model
|
|
cfg["terminal"] = map[string]interface{}{
|
|
"backend": "local",
|
|
"cwd": hermesWorkspaceDir,
|
|
}
|
|
if timezone != "" {
|
|
cfg["timezone"] = timezone
|
|
} else {
|
|
delete(cfg, "timezone")
|
|
}
|
|
if err := writeHermesConfigMap(configPath, cfg); err != nil {
|
|
return err
|
|
}
|
|
return writeHermesModelEnv(path.Join(confDir, ".env"), account)
|
|
}
|
|
|
|
func prepareHermesInstallFiles(appInstall *model.AppInstall, account *model.AgentAccount, modelName string) error {
|
|
if appInstall == nil {
|
|
return fmt.Errorf("app install is required")
|
|
}
|
|
dataDir := path.Join(appInstall.GetPath(), "data")
|
|
if err := writeHermesConfig(dataDir, account, modelName, normalizeHermesTimezone(common.LoadTimeZoneByCmd())); err != nil {
|
|
return err
|
|
}
|
|
return files.NewFileOp().ChownR(dataDir, "1000", "1000", true)
|
|
}
|
|
|
|
func readHermesConfig(configPath string) (*hermesConfig, error) {
|
|
content, err := files.NewFileOp().GetContent(configPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var cfg hermesConfig
|
|
if err := yaml.Unmarshal(content, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
func readHermesConfigMap(configPath string) (map[string]interface{}, error) {
|
|
fileOp := files.NewFileOp()
|
|
if !fileOp.Stat(configPath) {
|
|
return map[string]interface{}{}, nil
|
|
}
|
|
content, err := fileOp.GetContent(configPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg := map[string]interface{}{}
|
|
if strings.TrimSpace(string(content)) == "" {
|
|
return cfg, nil
|
|
}
|
|
if err := yaml.Unmarshal(content, &cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func writeHermesConfigMap(configPath string, cfg map[string]interface{}) error {
|
|
payload, err := yaml.Marshal(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return files.NewFileOp().SaveFile(configPath, string(payload), 0600)
|
|
}
|
|
|
|
func readHermesTelegramChannelConfig(confDir string) (*dto.AgentTelegramConfig, error) {
|
|
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg, err := readHermesConfigMap(path.Join(confDir, "config.yaml"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allowFrom := splitHermesEnvList(envMap["TELEGRAM_ALLOWED_USERS"])
|
|
requireMention := extractBoolValue(childMap(cfg, "telegram")["require_mention"], false)
|
|
dmPolicy := ""
|
|
if extractHermesEnvBool(envMap, "TELEGRAM_ALLOW_ALL_USERS", false) {
|
|
dmPolicy = "open"
|
|
} else if len(allowFrom) > 0 {
|
|
dmPolicy = "allowlist"
|
|
} else if envMap["TELEGRAM_BOT_TOKEN"] != "" {
|
|
dmPolicy = "pairing"
|
|
}
|
|
groupPolicy := ""
|
|
if requireMention {
|
|
groupPolicy = "allowlist"
|
|
} else if envMap["TELEGRAM_BOT_TOKEN"] != "" {
|
|
groupPolicy = "open"
|
|
}
|
|
token := envMap["TELEGRAM_BOT_TOKEN"]
|
|
result := &dto.AgentTelegramConfig{
|
|
Enabled: token != "",
|
|
DmPolicy: dmPolicy,
|
|
AllowFrom: allowFrom,
|
|
RequireMention: requireMention,
|
|
GroupPolicy: groupPolicy,
|
|
GroupAllowFrom: []string{},
|
|
Streaming: "",
|
|
DefaultAccount: "",
|
|
}
|
|
if token != "" {
|
|
result.Bots = []dto.AgentTelegramBot{
|
|
{
|
|
AgentChannelBotBase: dto.AgentChannelBotBase{
|
|
AccountID: "default",
|
|
Name: "Default",
|
|
Enabled: true,
|
|
IsDefault: true,
|
|
},
|
|
BotToken: token,
|
|
DmPolicy: dmPolicy,
|
|
GroupPolicy: groupPolicy,
|
|
Streaming: "",
|
|
},
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func writeHermesTelegramChannelConfig(confDir string, config dto.AgentTelegramConfig) error {
|
|
envPath := path.Join(confDir, ".env")
|
|
envMap, err := readAgentEnvMap(envPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
token := firstHermesTelegramBotToken(config.Bots, config.DefaultAccount)
|
|
if token != "" {
|
|
envMap["TELEGRAM_BOT_TOKEN"] = token
|
|
} else {
|
|
delete(envMap, "TELEGRAM_BOT_TOKEN")
|
|
}
|
|
delete(envMap, "TELEGRAM_ALLOWED_USERS")
|
|
delete(envMap, "TELEGRAM_ALLOW_ALL_USERS")
|
|
switch config.DmPolicy {
|
|
case "open":
|
|
envMap["TELEGRAM_ALLOW_ALL_USERS"] = "true"
|
|
case "allowlist":
|
|
if allow := joinHermesEnvList(config.AllowFrom); allow != "" {
|
|
envMap["TELEGRAM_ALLOWED_USERS"] = allow
|
|
}
|
|
}
|
|
if err := writeAgentEnvMap(envPath, envMap, []string{
|
|
"TELEGRAM_BOT_TOKEN",
|
|
"TELEGRAM_ALLOWED_USERS",
|
|
"TELEGRAM_ALLOW_ALL_USERS",
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
configPath := path.Join(confDir, "config.yaml")
|
|
cfg, err := readHermesConfigMap(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
telegram := ensureChildMap(cfg, "telegram")
|
|
telegram["require_mention"] = config.RequireMention
|
|
return writeHermesConfigMap(configPath, cfg)
|
|
}
|
|
|
|
func readHermesDiscordChannelConfig(confDir string) (*dto.AgentDiscordConfig, error) {
|
|
envMap, err := readAgentEnvMap(path.Join(confDir, ".env"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cfg, err := readHermesConfigMap(path.Join(confDir, "config.yaml"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allowFrom := splitHermesEnvList(envMap["DISCORD_ALLOWED_USERS"])
|
|
requireMention := extractBoolValue(childMap(cfg, "discord")["require_mention"], false)
|
|
dmPolicy := ""
|
|
if extractHermesEnvBool(envMap, "DISCORD_ALLOW_ALL_USERS", false) {
|
|
dmPolicy = "open"
|
|
} else if len(allowFrom) > 0 {
|
|
dmPolicy = "allowlist"
|
|
} else if envMap["DISCORD_BOT_TOKEN"] != "" {
|
|
dmPolicy = "pairing"
|
|
}
|
|
groupPolicy := ""
|
|
if requireMention {
|
|
groupPolicy = "allowlist"
|
|
} else if envMap["DISCORD_BOT_TOKEN"] != "" {
|
|
groupPolicy = "open"
|
|
}
|
|
token := envMap["DISCORD_BOT_TOKEN"]
|
|
result := &dto.AgentDiscordConfig{
|
|
Enabled: token != "",
|
|
DmPolicy: dmPolicy,
|
|
AllowFrom: allowFrom,
|
|
RequireMention: requireMention,
|
|
GroupPolicy: groupPolicy,
|
|
DefaultAccount: "",
|
|
}
|
|
if token != "" {
|
|
result.Bots = []dto.AgentDiscordBot{
|
|
{
|
|
AgentChannelBotBase: dto.AgentChannelBotBase{
|
|
AccountID: "default",
|
|
Name: "Default",
|
|
Enabled: true,
|
|
IsDefault: true,
|
|
},
|
|
Token: token,
|
|
},
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func writeHermesDiscordChannelConfig(confDir string, config dto.AgentDiscordConfig) error {
|
|
envPath := path.Join(confDir, ".env")
|
|
envMap, err := readAgentEnvMap(envPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
token := firstHermesDiscordBotToken(config.Bots, config.DefaultAccount)
|
|
if token != "" {
|
|
envMap["DISCORD_BOT_TOKEN"] = token
|
|
} else {
|
|
delete(envMap, "DISCORD_BOT_TOKEN")
|
|
}
|
|
delete(envMap, "DISCORD_ALLOWED_USERS")
|
|
delete(envMap, "DISCORD_ALLOW_ALL_USERS")
|
|
switch config.DmPolicy {
|
|
case "open":
|
|
envMap["DISCORD_ALLOW_ALL_USERS"] = "true"
|
|
case "allowlist":
|
|
if allow := joinHermesEnvList(config.AllowFrom); allow != "" {
|
|
envMap["DISCORD_ALLOWED_USERS"] = allow
|
|
}
|
|
}
|
|
if err := writeAgentEnvMap(envPath, envMap, []string{
|
|
"DISCORD_BOT_TOKEN",
|
|
"DISCORD_ALLOWED_USERS",
|
|
"DISCORD_ALLOW_ALL_USERS",
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
configPath := path.Join(confDir, "config.yaml")
|
|
cfg, err := readHermesConfigMap(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
discord := ensureChildMap(cfg, "discord")
|
|
discord["require_mention"] = config.RequireMention
|
|
return writeHermesConfigMap(configPath, cfg)
|
|
}
|
|
|
|
func deleteHermesEnvKeys(confDir string, keys ...string) error {
|
|
envPath := path.Join(confDir, ".env")
|
|
envMap, err := readAgentEnvMap(envPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, key := range keys {
|
|
delete(envMap, key)
|
|
}
|
|
return writeAgentEnvMap(envPath, envMap, keys)
|
|
}
|
|
|
|
func deleteHermesConfigSections(confDir string, topLevelKeys []string, platformKeys []string) error {
|
|
configPath := path.Join(confDir, "config.yaml")
|
|
cfg, err := readHermesConfigMap(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, key := range topLevelKeys {
|
|
delete(cfg, key)
|
|
}
|
|
if len(platformKeys) > 0 {
|
|
if platforms, ok := cfg["platforms"].(map[string]interface{}); ok {
|
|
for _, key := range platformKeys {
|
|
delete(platforms, key)
|
|
}
|
|
if len(platforms) == 0 {
|
|
delete(cfg, "platforms")
|
|
}
|
|
}
|
|
}
|
|
return writeHermesConfigMap(configPath, cfg)
|
|
}
|
|
|
|
func deleteHermesTelegramChannelConfig(confDir string) error {
|
|
if err := deleteHermesEnvKeys(confDir,
|
|
"TELEGRAM_BOT_TOKEN",
|
|
"TELEGRAM_ALLOWED_USERS",
|
|
"TELEGRAM_ALLOW_ALL_USERS",
|
|
"TELEGRAM_HOME_CHANNEL",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
return deleteHermesConfigSections(confDir, []string{"telegram"}, []string{"telegram"})
|
|
}
|
|
|
|
func deleteHermesDiscordChannelConfig(confDir string) error {
|
|
if err := deleteHermesEnvKeys(confDir,
|
|
"DISCORD_BOT_TOKEN",
|
|
"DISCORD_ALLOWED_USERS",
|
|
"DISCORD_ALLOW_ALL_USERS",
|
|
"DISCORD_HOME_CHANNEL",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
return deleteHermesConfigSections(confDir, []string{"discord"}, []string{"discord"})
|
|
}
|
|
|
|
func normalizeHermesTimezone(timezone string) string {
|
|
timezone = strings.TrimSpace(timezone)
|
|
if timezone == "" {
|
|
return ""
|
|
}
|
|
if _, err := time.LoadLocation(timezone); err != nil {
|
|
return ""
|
|
}
|
|
return timezone
|
|
}
|
|
|
|
func resolveHermesProvider(provider, apiType string) string {
|
|
if apiType == "anthropic-messages" && (provider == "deepseek" || provider == "bailian-coding-plan" || provider == "ark-coding-plan" || provider == "xiaomi") {
|
|
return "anthropic"
|
|
}
|
|
switch provider {
|
|
case "":
|
|
return "custom"
|
|
case "moonshot":
|
|
return "kimi-coding"
|
|
case "kimi":
|
|
return "kimi-coding-cn"
|
|
case "openrouter", "anthropic", "deepseek", "gemini", "zai", "kimi-coding", "xiaomi":
|
|
return provider
|
|
case "minimax":
|
|
return "minimax-cn"
|
|
default:
|
|
return "custom"
|
|
}
|
|
}
|
|
|
|
func resolveHermesModel(sourceProvider, targetProvider, modelName string) string {
|
|
target := strings.TrimSpace(modelName)
|
|
if target == "" {
|
|
return ""
|
|
}
|
|
if targetProvider != "custom" {
|
|
return target
|
|
}
|
|
return providercatalog.NormalizeModelID(sourceProvider, target)
|
|
}
|
|
|
|
func resolveHermesConfiguredModelID(account *model.AgentAccount, accountModels []dto.AgentAccountModel, configuredModel string) (string, error) {
|
|
if account == nil {
|
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
|
}
|
|
configuredModel = strings.TrimSpace(configuredModel)
|
|
if configuredModel == "" {
|
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
|
}
|
|
provider := resolveHermesProvider(account.Provider, account.APIType)
|
|
for _, item := range accountModels {
|
|
if resolveHermesModel(account.Provider, provider, item.ID) == configuredModel {
|
|
return item.ID, nil
|
|
}
|
|
}
|
|
return "", buserr.New("ErrAgentModelNotInAccount")
|
|
}
|
|
|
|
func resolveHermesEnvEntries(account *model.AgentAccount) []hermesEnvEntry {
|
|
if account == nil {
|
|
return nil
|
|
}
|
|
resolvedProvider := resolveHermesProvider(account.Provider, account.APIType)
|
|
if resolvedProvider == "custom" {
|
|
if account.APIKey == "" {
|
|
return nil
|
|
}
|
|
return []hermesEnvEntry{
|
|
{Key: "CUSTOM_API_KEY", Value: account.APIKey},
|
|
}
|
|
}
|
|
apiKey := account.APIKey
|
|
baseURL := account.BaseURL
|
|
entries := make([]hermesEnvEntry, 0, 4)
|
|
|
|
appendEntry := func(key, value string) {
|
|
if key == "" || value == "" {
|
|
return
|
|
}
|
|
entries = append(entries, hermesEnvEntry{Key: key, Value: value})
|
|
}
|
|
if resolvedProvider == "anthropic" {
|
|
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
|
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
|
return entries
|
|
}
|
|
|
|
switch account.Provider {
|
|
case "openrouter":
|
|
appendEntry("OPENROUTER_API_KEY", apiKey)
|
|
appendEntry("OPENROUTER_BASE_URL", baseURL)
|
|
case "anthropic":
|
|
appendEntry("ANTHROPIC_API_KEY", apiKey)
|
|
appendEntry("ANTHROPIC_BASE_URL", baseURL)
|
|
case "gemini":
|
|
appendEntry("GOOGLE_API_KEY", apiKey)
|
|
appendEntry("GEMINI_API_KEY", apiKey)
|
|
appendEntry("GEMINI_BASE_URL", baseURL)
|
|
case "zai":
|
|
appendEntry("GLM_API_KEY", apiKey)
|
|
appendEntry("ZAI_API_KEY", apiKey)
|
|
appendEntry("Z_AI_API_KEY", apiKey)
|
|
appendEntry("GLM_BASE_URL", baseURL)
|
|
case "kimi-coding", "moonshot":
|
|
appendEntry("KIMI_API_KEY", apiKey)
|
|
appendEntry("KIMI_BASE_URL", baseURL)
|
|
case "kimi":
|
|
appendEntry("KIMI_CN_API_KEY", apiKey)
|
|
appendEntry("KIMI_BASE_URL", baseURL)
|
|
case "minimax":
|
|
appendEntry("MINIMAX_CN_API_KEY", apiKey)
|
|
appendEntry("MINIMAX_CN_BASE_URL", baseURL)
|
|
case "xiaomi":
|
|
appendEntry("XIAOMI_API_KEY", apiKey)
|
|
appendEntry("XIAOMI_BASE_URL", baseURL)
|
|
case "deepseek":
|
|
appendEntry("DEEPSEEK_API_KEY", apiKey)
|
|
appendEntry("DEEPSEEK_BASE_URL", baseURL)
|
|
case "bailian-coding-plan":
|
|
appendEntry("DASHSCOPE_API_KEY", apiKey)
|
|
appendEntry("DASHSCOPE_BASE_URL", baseURL)
|
|
case "openai":
|
|
appendEntry("OPENAI_API_KEY", apiKey)
|
|
appendEntry("OPENAI_BASE_URL", baseURL)
|
|
case "custom", "vllm":
|
|
if account.APIType == "openai-completions" || account.APIType == "openai-responses" {
|
|
appendEntry("OPENAI_API_KEY", apiKey)
|
|
appendEntry("OPENAI_BASE_URL", baseURL)
|
|
}
|
|
default:
|
|
if envKey := providercatalog.EnvKey(account.Provider); envKey != "" {
|
|
appendEntry(envKey, apiKey)
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
func writeHermesModelEnv(envPath string, account *model.AgentAccount) error {
|
|
envMap, err := readAgentEnvMap(envPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, key := range hermesManagedModelEnvKeys() {
|
|
delete(envMap, key)
|
|
}
|
|
entries := resolveHermesEnvEntries(account)
|
|
order := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.Key == "" || entry.Value == "" {
|
|
continue
|
|
}
|
|
envMap[entry.Key] = entry.Value
|
|
order = append(order, entry.Key)
|
|
}
|
|
return writeAgentEnvMap(envPath, envMap, order)
|
|
}
|
|
|
|
func hermesManagedModelEnvKeys() []string {
|
|
keys := []string{
|
|
"OPENROUTER_API_KEY",
|
|
"OPENROUTER_BASE_URL",
|
|
"ANTHROPIC_API_KEY",
|
|
"ANTHROPIC_BASE_URL",
|
|
"GOOGLE_API_KEY",
|
|
"GEMINI_API_KEY",
|
|
"GEMINI_BASE_URL",
|
|
"GLM_API_KEY",
|
|
"ZAI_API_KEY",
|
|
"Z_AI_API_KEY",
|
|
"GLM_BASE_URL",
|
|
"KIMI_API_KEY",
|
|
"KIMI_CN_API_KEY",
|
|
"KIMI_BASE_URL",
|
|
"MINIMAX_CN_API_KEY",
|
|
"MINIMAX_CN_BASE_URL",
|
|
"XIAOMI_API_KEY",
|
|
"XIAOMI_BASE_URL",
|
|
"DEEPSEEK_API_KEY",
|
|
"DEEPSEEK_BASE_URL",
|
|
"DASHSCOPE_API_KEY",
|
|
"DASHSCOPE_BASE_URL",
|
|
"OPENAI_API_KEY",
|
|
"OPENAI_BASE_URL",
|
|
}
|
|
seen := make(map[string]struct{}, len(keys))
|
|
result := make([]string, 0, len(keys))
|
|
appendKey := func(key string) {
|
|
if key == "" {
|
|
return
|
|
}
|
|
if _, ok := seen[key]; ok {
|
|
return
|
|
}
|
|
seen[key] = struct{}{}
|
|
result = append(result, key)
|
|
}
|
|
for _, key := range keys {
|
|
appendKey(key)
|
|
}
|
|
for _, meta := range providercatalog.All() {
|
|
appendKey(meta.EnvKey)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func splitHermesEnvList(value string) []string {
|
|
if value == "" {
|
|
return []string{}
|
|
}
|
|
parts := strings.Split(value, ",")
|
|
result := make([]string, 0, len(parts))
|
|
seen := make(map[string]struct{}, len(parts))
|
|
for _, part := range parts {
|
|
item := strings.TrimSpace(part)
|
|
if item == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[item]; ok {
|
|
continue
|
|
}
|
|
seen[item] = struct{}{}
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func joinHermesEnvList(values []string) string {
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
result := make([]string, 0, len(values))
|
|
seen := make(map[string]struct{}, len(values))
|
|
for _, value := range values {
|
|
item := strings.TrimSpace(value)
|
|
if item == "" {
|
|
continue
|
|
}
|
|
if _, ok := seen[item]; ok {
|
|
continue
|
|
}
|
|
seen[item] = struct{}{}
|
|
result = append(result, item)
|
|
}
|
|
return strings.Join(result, ",")
|
|
}
|
|
|
|
func firstHermesTelegramBotToken(bots []dto.AgentTelegramBot, defaultAccount string) string {
|
|
for _, bot := range bots {
|
|
if bot.AccountID == defaultAccount || bot.IsDefault {
|
|
return bot.BotToken
|
|
}
|
|
}
|
|
if len(bots) == 0 {
|
|
return ""
|
|
}
|
|
return bots[0].BotToken
|
|
}
|
|
|
|
func firstHermesDiscordBotToken(bots []dto.AgentDiscordBot, defaultAccount string) string {
|
|
for _, bot := range bots {
|
|
if bot.AccountID == defaultAccount || bot.IsDefault {
|
|
return bot.Token
|
|
}
|
|
}
|
|
if len(bots) == 0 {
|
|
return ""
|
|
}
|
|
return bots[0].Token
|
|
}
|
|
|
|
func extractHermesEnvBool(envMap map[string]string, key string, defaultValue bool) bool {
|
|
value, ok := envMap[key]
|
|
if !ok || value == "" {
|
|
return defaultValue
|
|
}
|
|
return strings.EqualFold(value, "true")
|
|
}
|
|
|
|
func validateHermesPairingApproveResult(output string, err error) error {
|
|
if strings.Contains(output, "Approved!") {
|
|
return nil
|
|
}
|
|
if strings.Contains(output, "not found or expired for platform") {
|
|
return buserr.New("ErrHermesPairingCodeUnavailable")
|
|
}
|
|
if err == nil {
|
|
if strings.TrimSpace(output) == "" {
|
|
return fmt.Errorf("unexpected hermes pairing approve result")
|
|
}
|
|
return errors.New(strings.TrimSpace(output))
|
|
}
|
|
if strings.Contains(err.Error(), "not found or expired for platform") {
|
|
return buserr.New("ErrHermesPairingCodeUnavailable")
|
|
}
|
|
return err
|
|
}
|