mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
1710 lines
54 KiB
Go
1710 lines
54 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/xpack"
|
|
"github.com/docker/docker/api/types/container"
|
|
"gopkg.in/yaml.v3"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var agentAccountMutationMu sync.Mutex
|
|
|
|
type IAgentService interface {
|
|
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
|
|
BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error)
|
|
BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error)
|
|
BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error)
|
|
BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error)
|
|
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
|
|
DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error)
|
|
Delete(req dto.AgentDeleteReq) error
|
|
ResetToken(req dto.AgentTokenResetReq) error
|
|
UpdateRemark(req dto.AgentRemarkUpdateReq) error
|
|
BindWebsite(req dto.AgentWebsiteBindReq) error
|
|
UnbindWebsite(req dto.AgentIDReq) error
|
|
GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, error)
|
|
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
|
|
GetHermesChatSessions(req dto.AgentIDReq) ([]dto.AgentHermesChatSessionItem, error)
|
|
RenameHermesChatSession(req dto.AgentHermesChatSessionRenameReq) error
|
|
DeleteHermesChatSession(req dto.AgentHermesChatSessionDeleteReq) error
|
|
GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error)
|
|
GetProviders() ([]dto.ProviderInfo, error)
|
|
GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error)
|
|
UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error
|
|
GetOtherConfig(req dto.AgentIDReq) (*dto.AgentOtherConfig, error)
|
|
UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error
|
|
GetConfigFile(req dto.AgentConfigFileReq) (*dto.AgentConfigFile, error)
|
|
UpdateConfigFile(req dto.AgentConfigFileUpdateReq) error
|
|
ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, error)
|
|
SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error)
|
|
UpdateSkill(req dto.AgentSkillUpdateReq) error
|
|
InstallSkill(req dto.AgentSkillInstallReq) error
|
|
UninstallSkill(req dto.AgentSkillUninstallReq) error
|
|
|
|
CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCreateResp, error)
|
|
DeleteRole(req dto.AgentRoleDeleteReq) error
|
|
BindRole(req dto.AgentRoleBindReq) error
|
|
UnbindRole(req dto.AgentRoleBindReq) error
|
|
GetConfiguredAgents(req dto.AgentConfiguredAgentsReq) ([]dto.AgentConfiguredAgentItem, error)
|
|
GetRoleChannels(req dto.AgentRoleChannelsReq) ([]dto.AgentRoleChannelItem, error)
|
|
GetRoleMarkdownFiles(req dto.AgentRoleMarkdownFilesReq) ([]dto.AgentRoleMarkdownFileItem, error)
|
|
UpdateRoleMarkdownFiles(req dto.AgentRoleMarkdownFilesUpdateReq) error
|
|
|
|
CreateAccount(req dto.AgentAccountCreateReq) error
|
|
UpdateAccount(req dto.AgentAccountUpdateReq) error
|
|
SyncAgentsByAccount(account *model.AgentAccount) error
|
|
PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error)
|
|
CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error)
|
|
GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error)
|
|
DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error)
|
|
CreateAccountModel(req dto.AgentAccountModelCreateReq) error
|
|
UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error
|
|
DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error
|
|
VerifyAccount(req dto.AgentAccountVerifyReq) error
|
|
DeleteAccount(req dto.AgentAccountDeleteReq) error
|
|
|
|
GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error)
|
|
UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error
|
|
GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error)
|
|
UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error
|
|
GetDiscordConfig(req dto.AgentIDReq) (*dto.AgentDiscordConfig, error)
|
|
UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error
|
|
GetWecomConfig(req dto.AgentIDReq) (*dto.AgentWecomConfig, error)
|
|
UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error
|
|
GetDingTalkConfig(req dto.AgentIDReq) (*dto.AgentDingTalkConfig, error)
|
|
UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error
|
|
GetWeixinConfig(req dto.AgentIDReq) (*dto.AgentWeixinConfig, error)
|
|
LoginWeixinChannel(req dto.AgentWeixinLoginReq) error
|
|
GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig, error)
|
|
UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error
|
|
DeleteChannelConfig(req dto.AgentChannelDeleteReq) error
|
|
InstallPlugin(req dto.AgentPluginInstallReq) error
|
|
UpgradePlugin(req dto.AgentPluginUpgradeReq) error
|
|
UninstallPlugin(req dto.AgentPluginUninstallReq) error
|
|
CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error)
|
|
ListPlugins(req dto.AgentPluginsReq) ([]dto.AgentPluginItem, error)
|
|
SearchPlugins(req dto.AgentPluginSearchReq) ([]dto.AgentPluginSearchItem, error)
|
|
InstallMarketPlugin(req dto.AgentPluginMarketInstallReq) error
|
|
OperatePlugin(req dto.AgentPluginOperateReq) error
|
|
ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error
|
|
}
|
|
|
|
type batchUpgradePlan struct {
|
|
ctx batchAgentInstallContext
|
|
req request.AppInstallUpgrade
|
|
}
|
|
|
|
type batchAgentInstallContext struct {
|
|
agent model.Agent
|
|
install model.AppInstall
|
|
}
|
|
|
|
const (
|
|
defaultBrowserExecutablePath = "/home/node/.cache/ms-playwright/openclaw-browser"
|
|
defaultBrowserProfile = "openclaw"
|
|
defaultUserTimezone = "Asia/Shanghai"
|
|
defaultToolsProfile = "full"
|
|
defaultToolsSessionVisibility = "all"
|
|
maxCommunityAIAgents = int64(5)
|
|
openclawPluginBaseDir = "/home/node/.openclaw/extensions"
|
|
openclawPluginPackageTmpDir = "/tmp/openclaw-plugin"
|
|
openclawManagedSkillsDir = "/home/node/.openclaw/skills"
|
|
openclawGatewayPort = 18789
|
|
openclawAllowedOriginHost = "127.0.0.1"
|
|
openclawHTTPSVersion = "2026.3.13"
|
|
openclawHTTPVersion = "2026.3.23"
|
|
openclawTrustedProxyLoopback = "127.0.0.1/32"
|
|
defaultOpenclawNPMRegistry = "https://registry.npmjs.org/"
|
|
)
|
|
|
|
func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {
|
|
agentType := req.AgentType
|
|
if err := checkPortExist(req.WebUIPort); err != nil {
|
|
return nil, err
|
|
}
|
|
if exist, _ := agentRepo.GetFirst(repo.WithByLowerName(req.Name)); exist != nil && exist.ID > 0 {
|
|
return nil, buserr.New("ErrNameIsExist")
|
|
}
|
|
if installs, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(installs) > 0 {
|
|
return nil, buserr.New("ErrNameIsExist")
|
|
}
|
|
if !global.CONF.Base.IsEnterprise && !xpack.MultiNodeProvider.IsXpack() {
|
|
count, _, err := agentRepo.Page(1, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count >= maxCommunityAIAgents {
|
|
return nil, buserr.WithMap("ErrAgentLimitReached", map[string]interface{}{"max": maxCommunityAIAgents}, nil)
|
|
}
|
|
}
|
|
app, err := appRepo.GetFirst(appRepo.WithKey(agentType))
|
|
if err != nil || app.ID == 0 {
|
|
return nil, buserr.New("ErrRecordNotFound")
|
|
}
|
|
detail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(req.AppVersion))
|
|
if err != nil || detail.ID == 0 {
|
|
return nil, buserr.New("ErrRecordNotFound")
|
|
}
|
|
|
|
provider := ""
|
|
baseURL := ""
|
|
apiType := ""
|
|
apiKey := ""
|
|
runtimeModel := ""
|
|
accountID := uint(0)
|
|
token := ""
|
|
configPath := ""
|
|
storedModel := ""
|
|
var allowedOrigins []string
|
|
var account *model.AgentAccount
|
|
var installHooks *appInstallHooks
|
|
var dashboardAuth agentDashboardAuth
|
|
|
|
if agentType == constant.AppOpenclaw || agentType == constant.AppHermesAgent {
|
|
if req.AccountID == 0 {
|
|
return nil, buserr.New("ErrAgentAccountRequired")
|
|
}
|
|
account, err = agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !account.Verified {
|
|
return nil, buserr.New("ErrAgentAccountNotVerified")
|
|
}
|
|
provider = account.Provider
|
|
baseURL = account.BaseURL
|
|
resolvedRuntime, err := resolveOpenclawAccountModelRuntimeByID(account, req.Model)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
storedModel = resolvedRuntime.StoredModel
|
|
apiType = resolvedRuntime.APIType
|
|
runtimeModel = resolvedRuntime.PrimaryModel
|
|
apiKey = account.APIKey
|
|
accountID = account.ID
|
|
}
|
|
|
|
if agentType == constant.AppOpenclaw {
|
|
var err error
|
|
allowedOrigins, err = normalizeAllowedOrigins(req.AllowedOrigins)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(allowedOrigins) == 0 {
|
|
return nil, fmt.Errorf("allowed origins is required")
|
|
}
|
|
token = strings.TrimSpace(req.Token)
|
|
if token == "" {
|
|
token = generateToken()
|
|
}
|
|
installHooks = &appInstallHooks{
|
|
AfterCopyData: func(appInstall *model.AppInstall) error {
|
|
return prepareOpenclawInstallFiles(appInstall, account, storedModel, token, allowedOrigins)
|
|
},
|
|
}
|
|
} else if agentType == constant.AppHermesAgent {
|
|
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
installHooks = &appInstallHooks{
|
|
AfterCopyData: func(appInstall *model.AppInstall) error {
|
|
if err := prepareHermesInstallFiles(appInstall, account, storedModel); err != nil {
|
|
return err
|
|
}
|
|
return writeAgentDashboardAuthEnv(appInstall.GetEnvPath(), agentType, dashboardAuth, false)
|
|
},
|
|
}
|
|
} else if agentType == constant.AppCopaw {
|
|
dashboardAuth = normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
}
|
|
|
|
params := map[string]interface{}{
|
|
constant.CPUS: "0",
|
|
constant.MemoryLimit: "0",
|
|
constant.HostIP: "",
|
|
}
|
|
setAgentWebUIParams(params, agentType, detail.Version, req.WebUIPort)
|
|
if agentType == constant.AppOpenclaw {
|
|
if allowedOrigin := firstAllowedOrigin(allowedOrigins); allowedOrigin != "" {
|
|
params["ALLOWED_ORIGIN"] = allowedOrigin
|
|
}
|
|
params["PROVIDER"] = provider
|
|
params["MODEL"] = runtimeModel
|
|
params["API_TYPE"] = apiType
|
|
params["BASE_URL"] = baseURL
|
|
params["API_KEY"] = apiKey
|
|
params["OPENCLAW_GATEWAY_TOKEN"] = token
|
|
}
|
|
if usernameKey, passwordKey, ok := agentDashboardAuthEnvKeys(agentType); ok {
|
|
params[usernameKey] = dashboardAuth.Username
|
|
params[passwordKey] = dashboardAuth.Password
|
|
if agentType == constant.AppCopaw {
|
|
params[qwenPawAuthEnabledEnvKey] = "true"
|
|
}
|
|
}
|
|
|
|
if req.EditCompose && strings.TrimSpace(req.DockerCompose) == "" {
|
|
return nil, buserr.New("ErrAgentComposeRequired")
|
|
}
|
|
installReq := request.AppInstallCreate{
|
|
AppDetailId: detail.ID,
|
|
Name: req.Name,
|
|
Params: params,
|
|
TaskID: req.TaskID,
|
|
AppContainerConfig: request.AppContainerConfig{
|
|
Advanced: req.Advanced,
|
|
ContainerName: req.ContainerName,
|
|
AllowPort: req.AllowPort,
|
|
SpecifyIP: req.SpecifyIP,
|
|
RestartPolicy: req.RestartPolicy,
|
|
CpuQuota: req.CpuQuota,
|
|
MemoryLimit: req.MemoryLimit,
|
|
MemoryUnit: req.MemoryUnit,
|
|
PullImage: req.PullImage,
|
|
EditCompose: req.EditCompose,
|
|
DockerCompose: req.DockerCompose,
|
|
},
|
|
}
|
|
appInstall, err := AppService{}.installWithHooks(installReq, false, installHooks)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if agentType == constant.AppOpenclaw {
|
|
configPath = path.Join(appInstall.GetPath(), "data", "conf", "openclaw.json")
|
|
}
|
|
if agentType == constant.AppHermesAgent {
|
|
configPath = path.Join(appInstall.GetPath(), "data", "config.yaml")
|
|
}
|
|
agent := &model.Agent{
|
|
Name: req.Name,
|
|
Remark: req.Remark,
|
|
AgentType: agentType,
|
|
Provider: provider,
|
|
Model: storedModel,
|
|
APIType: apiType,
|
|
BaseURL: baseURL,
|
|
APIKey: apiKey,
|
|
Token: token,
|
|
Status: appInstall.Status,
|
|
Message: appInstall.Message,
|
|
AppInstallID: appInstall.ID,
|
|
AccountID: accountID,
|
|
ConfigPath: configPath,
|
|
}
|
|
if err := agentRepo.Create(agent); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
item := buildAgentItem(agent, appInstall, nil)
|
|
return &item, nil
|
|
}
|
|
|
|
func (a AgentService) BatchInstall(req dto.AgentBatchInstallReq) (*dto.AgentItem, error) {
|
|
accountID, err := a.ensureBatchInstallAccount(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createReq := buildCreateReqFromBatchInstallReq(req)
|
|
createReq.AccountID = accountID
|
|
if strings.TrimSpace(createReq.Name) == "" {
|
|
name, err := buildBatchInstallAgentName(createReq.AgentType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createReq.Name = name
|
|
}
|
|
if createReq.AgentType == constant.AppOpenclaw && len(createReq.AllowedOrigins) == 0 {
|
|
allowedOrigin, err := buildBatchInstallOpenclawAllowedOrigin(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createReq.AllowedOrigins = []string{allowedOrigin}
|
|
}
|
|
return a.Create(createReq)
|
|
}
|
|
|
|
func (a AgentService) BatchUpgrade(req dto.AgentBatchUpgradeReq) ([]dto.AgentBatchUpgradeResult, error) {
|
|
plans, results, err := buildBatchUpgradePlans(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, plan := range plans {
|
|
result := dto.AgentBatchUpgradeResult{
|
|
AgentID: plan.ctx.agent.ID,
|
|
AgentName: plan.ctx.agent.Name,
|
|
AppInstallID: plan.ctx.agent.AppInstallID,
|
|
}
|
|
if err := upgradeInstall(plan.req); err != nil {
|
|
result.Message = err.Error()
|
|
} else {
|
|
result.Success = true
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (a AgentService) BatchInstallSkill(req dto.AgentBatchSkillInstallReq) ([]dto.AgentBatchSkillInstallResult, error) {
|
|
if req.AgentType != constant.AppOpenclaw && req.AgentType != constant.AppHermesAgent {
|
|
return nil, fmt.Errorf("%s does not support skill batch install", req.AgentType)
|
|
}
|
|
packagePath, err := validateLocalSkillPackagePath(req.PackagePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if format, err := localSkillPackageFormat(packagePath); err != nil {
|
|
return nil, err
|
|
} else if format != "zip" {
|
|
return nil, fmt.Errorf("only .zip skill packages can be installed")
|
|
}
|
|
skillName := sanitizeLocalSkillDirName(req.SkillName, packagePath, req.SkillName)
|
|
agents, err := listBatchAgents(req.AgentType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := make([]dto.AgentBatchSkillInstallResult, 0, len(agents))
|
|
for _, agent := range agents {
|
|
result := dto.AgentBatchSkillInstallResult{
|
|
AgentID: agent.ID,
|
|
AgentName: agent.Name,
|
|
AppInstallID: agent.AppInstallID,
|
|
}
|
|
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
|
if message != "" {
|
|
result.Message = message
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
install := ctx.install
|
|
result.AppInstallID = install.ID
|
|
if install.Status == constant.StatusInstalling || install.Status == constant.StatusUpgrading {
|
|
result.Message = fmt.Sprintf("agent status is %s", install.Status)
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
|
result.Message = err.Error()
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, buildBatchTaskID(req.TaskID, "batch-skill-install", agent.ID), agent.ID)
|
|
if err != nil {
|
|
result.Message = err.Error()
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
currentAgent := ctx.agent
|
|
currentInstall := ctx.install
|
|
installTask.AddSubTask("Install local skill", func(t *task.Task) error {
|
|
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute))
|
|
return installLocalSkillPackage(mgr, currentInstall.ContainerName, currentAgent.AgentType, currentAgent.ConfigPath, packagePath, skillName)
|
|
}, nil)
|
|
go func() {
|
|
if err := installTask.Execute(); err != nil {
|
|
global.LOG.Errorf("batch install local skill failed: %v", err)
|
|
}
|
|
}()
|
|
result.Success = true
|
|
results = append(results, result)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (a AgentService) BatchOperate(req dto.AgentBatchOperateReq) ([]dto.AgentBatchOperateResult, error) {
|
|
operate := constant.AppOperate(strings.TrimSpace(req.Operate))
|
|
if operate != constant.Start && operate != constant.Stop && operate != constant.Restart && operate != constant.Delete {
|
|
return nil, fmt.Errorf("operate %s is not supported", req.Operate)
|
|
}
|
|
agents, err := listBatchAgents(req.AgentType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := make([]dto.AgentBatchOperateResult, 0, len(agents))
|
|
for _, agent := range agents {
|
|
result := dto.AgentBatchOperateResult{
|
|
AgentID: agent.ID,
|
|
AgentName: agent.Name,
|
|
AppInstallID: agent.AppInstallID,
|
|
}
|
|
if operate == constant.Delete {
|
|
if err := a.Delete(dto.AgentDeleteReq{
|
|
ID: agent.ID,
|
|
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
|
ForceDelete: req.ForceDelete,
|
|
}); err != nil {
|
|
result.Message = err.Error()
|
|
} else {
|
|
result.Success = true
|
|
}
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
|
if message != "" {
|
|
result.Message = message
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
install := ctx.install
|
|
result.AppInstallID = install.ID
|
|
if message := batchOperateSkipMessage(operate, install.Status); message != "" {
|
|
result.Success = true
|
|
result.Skipped = true
|
|
result.Message = message
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
if err := NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
|
InstallId: install.ID,
|
|
Operate: operate,
|
|
TaskID: buildBatchTaskID(req.TaskID, "batch-operate", agent.ID),
|
|
}); err != nil {
|
|
result.Message = err.Error()
|
|
} else {
|
|
result.Success = true
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func listBatchAgents(agentType string) ([]model.Agent, error) {
|
|
return agentRepo.List(func(db *gorm.DB) *gorm.DB {
|
|
return db.Where("agent_type = ?", agentType).Order("id ASC")
|
|
})
|
|
}
|
|
|
|
func loadBatchAgentInstall(agent model.Agent, agentType string) (batchAgentInstallContext, string) {
|
|
ctx := batchAgentInstallContext{agent: agent}
|
|
if agent.AppInstallID == 0 {
|
|
return ctx, "agent app install id is empty"
|
|
}
|
|
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
|
|
if err != nil {
|
|
return ctx, err.Error()
|
|
}
|
|
ctx.install = install
|
|
if install.App.Key != agentType {
|
|
return ctx, fmt.Sprintf("app key %s does not match agent type %s", install.App.Key, agentType)
|
|
}
|
|
return ctx, ""
|
|
}
|
|
|
|
func buildBatchUpgradePlans(req dto.AgentBatchUpgradeReq) ([]batchUpgradePlan, []dto.AgentBatchUpgradeResult, error) {
|
|
agents, err := listBatchAgents(req.AgentType)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
plans := make([]batchUpgradePlan, 0, len(agents))
|
|
results := make([]dto.AgentBatchUpgradeResult, 0)
|
|
for _, agent := range agents {
|
|
result := dto.AgentBatchUpgradeResult{
|
|
AgentID: agent.ID,
|
|
AgentName: agent.Name,
|
|
AppInstallID: agent.AppInstallID,
|
|
}
|
|
ctx, message := loadBatchAgentInstall(agent, req.AgentType)
|
|
if message != "" {
|
|
result.Message = message
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
install := ctx.install
|
|
result.AppInstallID = install.ID
|
|
if install.Status == constant.StatusInstalling || install.Status == constant.StatusUpgrading {
|
|
result.Message = fmt.Sprintf("agent status is %s", install.Status)
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
if install.Version == req.TargetVersion {
|
|
result.Success = true
|
|
result.Skipped = true
|
|
result.Message = "already target version"
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
detail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(install.AppId), appDetailRepo.WithVersion(req.TargetVersion))
|
|
if err != nil {
|
|
result.Message = err.Error()
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
if detail.ID == 0 {
|
|
result.Message = fmt.Sprintf("target version %s not found", req.TargetVersion)
|
|
results = append(results, result)
|
|
continue
|
|
}
|
|
plans = append(plans, batchUpgradePlan{
|
|
ctx: ctx,
|
|
req: request.AppInstallUpgrade{
|
|
InstallID: install.ID,
|
|
DetailID: detail.ID,
|
|
Backup: req.Backup,
|
|
PullImage: req.PullImage,
|
|
TaskID: buildBatchTaskID(req.TaskID, "batch-upgrade", install.ID),
|
|
},
|
|
})
|
|
}
|
|
return plans, results, nil
|
|
}
|
|
|
|
func buildBatchTaskID(taskID, prefix string, id uint) string {
|
|
taskID = strings.TrimSpace(taskID)
|
|
if taskID == "" {
|
|
taskID = fmt.Sprintf("%s-%d-%d", prefix, id, time.Now().UnixNano())
|
|
}
|
|
return fmt.Sprintf("%s-%d", taskID, id)
|
|
}
|
|
|
|
func batchOperateSkipMessage(operate constant.AppOperate, status string) string {
|
|
switch status {
|
|
case constant.StatusInstalling, constant.StatusUpgrading, constant.StatusUninstalling, constant.StatusRebuilding:
|
|
return fmt.Sprintf("agent status is %s", status)
|
|
}
|
|
switch operate {
|
|
case constant.Start:
|
|
if status == constant.StatusRunning || status == constant.StatusStarting || status == constant.StatusRestarting {
|
|
return fmt.Sprintf("agent status is %s", status)
|
|
}
|
|
case constant.Stop:
|
|
if status != constant.StatusRunning {
|
|
return fmt.Sprintf("agent status is %s", status)
|
|
}
|
|
case constant.Restart:
|
|
if status == constant.StatusStarting {
|
|
return fmt.Sprintf("agent status is %s", status)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func buildCreateReqFromBatchInstallReq(req dto.AgentBatchInstallReq) dto.AgentCreateReq {
|
|
return dto.AgentCreateReq{
|
|
Name: req.Name,
|
|
Remark: req.Remark,
|
|
AppVersion: req.AppVersion,
|
|
WebUIPort: req.WebUIPort,
|
|
BridgePort: req.BridgePort,
|
|
AllowedOrigins: req.AllowedOrigins,
|
|
AgentType: req.AgentType,
|
|
Model: req.Model,
|
|
AccountID: req.AccountID,
|
|
Token: req.Token,
|
|
DashboardUsername: req.DashboardUsername,
|
|
DashboardPassword: req.DashboardPassword,
|
|
TaskID: req.TaskID,
|
|
Advanced: req.Advanced,
|
|
ContainerName: req.ContainerName,
|
|
AllowPort: req.AllowPort,
|
|
SpecifyIP: req.SpecifyIP,
|
|
RestartPolicy: req.RestartPolicy,
|
|
CpuQuota: req.CpuQuota,
|
|
MemoryLimit: req.MemoryLimit,
|
|
MemoryUnit: req.MemoryUnit,
|
|
PullImage: req.PullImage,
|
|
EditCompose: req.EditCompose,
|
|
DockerCompose: req.DockerCompose,
|
|
}
|
|
}
|
|
|
|
func buildBatchInstallAgentName(agentType string) (string, error) {
|
|
prefix := strings.ToLower(strings.TrimSpace(agentType))
|
|
if prefix == "" {
|
|
prefix = "agent"
|
|
}
|
|
for i := 1; i <= 10000; i++ {
|
|
name := fmt.Sprintf("%s-%d", prefix, i)
|
|
exists, err := agentNameExists(name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !exists {
|
|
return name, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no available agent name")
|
|
}
|
|
|
|
func agentNameExists(name string) (bool, error) {
|
|
if exist, err := agentRepo.GetFirst(repo.WithByLowerName(name)); err == nil && exist != nil && exist.ID > 0 {
|
|
return true, nil
|
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return false, err
|
|
}
|
|
installs, err := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(name))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(installs) > 0, nil
|
|
}
|
|
|
|
func buildBatchInstallOpenclawAllowedOrigin(req dto.AgentBatchInstallReq) (string, error) {
|
|
host := ""
|
|
if value, err := settingRepo.GetValueByKey("SystemIP"); err == nil {
|
|
host = strings.TrimSpace(value)
|
|
}
|
|
if host == "" {
|
|
host = strings.TrimSpace(req.FallbackAccessHost)
|
|
}
|
|
if host == "" {
|
|
host = "127.0.0.1"
|
|
}
|
|
return buildOpenclawAllowedOrigin(openclawAllowedOriginScheme(req.AppVersion), host, req.WebUIPort)
|
|
}
|
|
|
|
func (a AgentService) ensureBatchInstallAccount(req dto.AgentBatchInstallReq) (uint, error) {
|
|
if !requiresAgentAccount(req.AgentType) {
|
|
return 0, nil
|
|
}
|
|
if req.MasterAccountID == 0 {
|
|
return 0, buserr.New("ErrInvalidParams")
|
|
}
|
|
snapshot := req.AccountSnapshot
|
|
account, err := agentAccountRepo.GetFirst(agentAccountRepo.WithByMasterAccountID(req.MasterAccountID))
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return 0, err
|
|
}
|
|
if account == nil && global.IsMaster {
|
|
account, err = agentAccountRepo.GetFirst(repo.WithByID(req.MasterAccountID))
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return 0, err
|
|
}
|
|
}
|
|
if account == nil {
|
|
account = &model.AgentAccount{
|
|
MasterAccountID: req.MasterAccountID,
|
|
}
|
|
}
|
|
account.MasterAccountID = req.MasterAccountID
|
|
account.Provider = snapshot.Provider
|
|
account.Name = snapshot.Name
|
|
account.APIKey = snapshot.APIKey
|
|
account.RememberAPIKey = snapshot.RememberAPIKey
|
|
account.BaseURL = snapshot.BaseURL
|
|
account.APIType = snapshot.APIType
|
|
account.AuthMode = snapshot.AuthMode
|
|
account.Remark = snapshot.Remark
|
|
account.Verified = true
|
|
|
|
initialModels, err := buildInitialAgentAccountModels(account, req.AccountModels)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
verifyModel, err := resolveAgentAccountVerifyModel(account.Provider, snapshot.VerifyModel, initialModels)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
account.VerifyModel = verifyModel
|
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
|
if account.ID == 0 {
|
|
if err := tx.Create(account).Error; err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := tx.Save(account).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return replacePersistedAgentAccountModelsWithTx(tx, account.ID, initialModels)
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
return account.ID, nil
|
|
}
|
|
|
|
func requiresAgentAccount(agentType string) bool {
|
|
return agentType != constant.AppCopaw
|
|
}
|
|
|
|
func setAgentWebUIParams(params map[string]interface{}, agentType, appVersion string, webUIPort int) {
|
|
if agentType == constant.AppOpenclaw && isOpenclawHTTPSWindowVersion(appVersion) {
|
|
params["PANEL_APP_PORT_HTTPS"] = webUIPort
|
|
return
|
|
}
|
|
params["PANEL_APP_PORT_HTTP"] = webUIPort
|
|
}
|
|
|
|
func (a AgentService) Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error) {
|
|
var opts []repo.DBOption
|
|
if strings.TrimSpace(req.Info) != "" {
|
|
opts = append(opts, repo.WithByLikeName(req.Info))
|
|
}
|
|
count, list, err := agentRepo.Page(req.Page, req.PageSize, opts...)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
items := make([]dto.AgentItem, 0, len(list))
|
|
appInstalls := make([]model.AppInstall, 0, len(list))
|
|
for _, item := range list {
|
|
appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(item.AppInstallID))
|
|
appInstalls = append(appInstalls, appInstall)
|
|
}
|
|
syncAgentAppInstalls(appInstalls)
|
|
for index, item := range list {
|
|
appInstall := appInstalls[index]
|
|
envMap := readInstallEnv(appInstall.Env)
|
|
agentItem := buildAgentItem(&item, &appInstall, envMap)
|
|
agentItem.Upgradable = checkAgentUpgradable(appInstall)
|
|
items = append(items, agentItem)
|
|
}
|
|
if err := hydrateAgentWebsiteItems(items); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return count, items, nil
|
|
}
|
|
|
|
func (a AgentService) Delete(req dto.AgentDeleteReq) error {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resources, err := a.deleteCheckByAgent(agent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(resources) > 0 {
|
|
return buserr.New("ErrAgentWebsiteBound")
|
|
}
|
|
if agent.AppInstallID == 0 {
|
|
return agentRepo.DeleteByID(agent.ID)
|
|
}
|
|
operate := request.AppInstalledOperate{
|
|
InstallId: agent.AppInstallID,
|
|
Operate: constant.Delete,
|
|
TaskID: req.TaskID,
|
|
ForceDelete: req.ForceDelete,
|
|
}
|
|
if err := NewIAppInstalledService().Operate(operate); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a AgentService) DeleteCheck(req dto.AgentIDReq) ([]dto.AppResource, error) {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return a.deleteCheckByAgent(agent)
|
|
}
|
|
|
|
func (a AgentService) deleteCheckByAgent(agent *model.Agent) ([]dto.AppResource, error) {
|
|
if agent == nil || agent.WebsiteID == 0 {
|
|
return nil, nil
|
|
}
|
|
website, err := websiteRepo.GetFirst(repo.WithByID(agent.WebsiteID))
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
websiteName, err := loadAgentWebsiteResourceName(website)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []dto.AppResource{{Type: "website", Name: websiteName}}, nil
|
|
}
|
|
|
|
func syncAgentAppInstalls(appInstalls []model.AppInstall) {
|
|
if len(appInstalls) == 0 {
|
|
return
|
|
}
|
|
|
|
var containersMap map[string]container.Summary
|
|
cli, err := docker.NewClient()
|
|
if err == nil {
|
|
defer cli.Close()
|
|
containers, err := cli.ListAllContainers()
|
|
if err == nil {
|
|
containersMap = make(map[string]container.Summary, len(containers))
|
|
for _, contain := range containers {
|
|
containersMap[contain.Names[0]] = contain
|
|
}
|
|
}
|
|
}
|
|
|
|
for index := range appInstalls {
|
|
if appInstalls[index].ID == 0 || doNotNeedSync(appInstalls[index]) {
|
|
continue
|
|
}
|
|
synAppInstall(containersMap, &appInstalls[index], false)
|
|
}
|
|
}
|
|
|
|
func (a AgentService) ResetToken(req dto.AgentTokenResetReq) error {
|
|
agent, err := loadOpenclawAgentByID(req.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newToken := generateToken()
|
|
if newToken == "" {
|
|
return fmt.Errorf("generate token failed")
|
|
}
|
|
gatewayMap := ensureChildMap(conf, "gateway")
|
|
authMap := ensureChildMap(gatewayMap, "auth")
|
|
if _, ok := authMap["mode"]; !ok {
|
|
authMap["mode"] = "token"
|
|
}
|
|
authMap["token"] = newToken
|
|
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
|
|
return err
|
|
}
|
|
agent.Token = newToken
|
|
return agentRepo.Save(agent)
|
|
}
|
|
|
|
func (a AgentService) UpdateRemark(req dto.AgentRemarkUpdateReq) error {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
agent.Remark = req.Remark
|
|
return agentRepo.Save(agent)
|
|
}
|
|
|
|
func (a AgentService) GetModelConfig(req dto.AgentIDReq) (*dto.AgentModelConfig, error) {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if agent.AgentType == constant.AppHermesAgent {
|
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
accountModels, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
model, err := resolveHermesConfiguredModelID(account, accountModels, cfg.Model.Default)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto.AgentModelConfig{
|
|
AccountID: agent.AccountID,
|
|
Model: model,
|
|
Fallbacks: []string{},
|
|
}, nil
|
|
}
|
|
agent, _, conf, err := a.loadOpenclawAgentConfig(req.AgentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
models, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
model := extractOpenclawPrimaryModelID(conf, account, models)
|
|
if model == "" {
|
|
model = agent.Model
|
|
}
|
|
return &dto.AgentModelConfig{
|
|
AccountID: agent.AccountID,
|
|
Model: model,
|
|
Fallbacks: extractOpenclawFallbackModelIDs(conf, account, models, model),
|
|
}, nil
|
|
}
|
|
|
|
func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolvedRuntime, err := resolveOpenclawAccountModelRuntimeByID(account, req.Model)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
modelName := resolvedRuntime.StoredModel
|
|
apiType := resolvedRuntime.APIType
|
|
confDir := path.Dir(agent.ConfigPath)
|
|
if agent.AgentType == constant.AppHermesAgent {
|
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeHermesConfig(confDir, account, modelName, cfg.Timezone); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if agent.AgentType != constant.AppOpenclaw {
|
|
return fmt.Errorf("%s does not support", agent.AgentType)
|
|
}
|
|
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, req.Fallbacks); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
agent.Provider = account.Provider
|
|
agent.Model = modelName
|
|
agent.APIType = apiType
|
|
agent.BaseURL = account.BaseURL
|
|
agent.APIKey = account.APIKey
|
|
agent.AccountID = account.ID
|
|
return agentRepo.Save(agent)
|
|
}
|
|
|
|
func (a AgentService) GetProviders() ([]dto.ProviderInfo, error) {
|
|
definitions := providercatalog.All()
|
|
providers := make([]dto.ProviderInfo, 0, len(definitions))
|
|
for key, def := range definitions {
|
|
models := make([]dto.ProviderModelInfo, 0, len(def.Models))
|
|
for _, item := range def.Models {
|
|
models = append(models, dto.ProviderModelInfo{
|
|
ID: item.ID,
|
|
Name: item.Name,
|
|
})
|
|
}
|
|
apiTypes := make([]dto.ProviderAPIInfo, 0, len(def.APIConfigs))
|
|
for _, item := range def.APIConfigs {
|
|
apiModels := make([]dto.ProviderModelInfo, 0, len(item.Models))
|
|
for _, model := range item.Models {
|
|
apiModels = append(apiModels, dto.ProviderModelInfo{
|
|
ID: model.ID,
|
|
Name: model.Name,
|
|
})
|
|
}
|
|
apiTypes = append(apiTypes, dto.ProviderAPIInfo{
|
|
APIType: item.APIType,
|
|
BaseURL: item.BaseURL,
|
|
EditableBaseURL: item.EditableBaseURL,
|
|
SupportsModelDiscovery: item.DiscoverModels,
|
|
DefaultAuthMode: item.DefaultAuthMode,
|
|
AuthModes: item.AuthModes,
|
|
Models: apiModels,
|
|
})
|
|
}
|
|
baseURL, _ := providercatalog.DefaultBaseURL(key)
|
|
providers = append(providers, dto.ProviderInfo{
|
|
Sort: def.Sort,
|
|
Provider: key,
|
|
DisplayName: localizedAgentProviderName(key),
|
|
BaseURL: baseURL,
|
|
DefaultAPIType: def.DefaultAPIType,
|
|
APITypes: apiTypes,
|
|
Models: models,
|
|
})
|
|
}
|
|
sort.Slice(providers, func(i, j int) bool {
|
|
return providers[i].Sort < providers[j].Sort
|
|
})
|
|
return providers, nil
|
|
}
|
|
|
|
func (a AgentService) CreateAccount(req dto.AgentAccountCreateReq) error {
|
|
agentAccountMutationMu.Lock()
|
|
defer agentAccountMutationMu.Unlock()
|
|
provider := req.Provider
|
|
if err := ensureAgentAccountNameAvailable(provider, req.Name, 0); err != nil {
|
|
return err
|
|
}
|
|
initialModels, err := buildInitialAgentAccountModels(&model.AgentAccount{Provider: provider, APIType: req.APIType}, req.Models)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyModel, err := resolveAgentAccountVerifyModel(provider, req.VerifyModel, initialModels)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability
|
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
account := &model.AgentAccount{
|
|
Provider: resolvedInput.Provider,
|
|
Name: req.Name,
|
|
APIKey: resolvedInput.APIKey,
|
|
RememberAPIKey: req.RememberAPIKey,
|
|
BaseURL: resolvedInput.BaseURL,
|
|
APIType: resolvedInput.APIType,
|
|
AuthMode: resolvedInput.AuthMode,
|
|
VerifyModel: verifyModel,
|
|
Verified: true,
|
|
Remark: req.Remark,
|
|
}
|
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(account).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(initialModels) == 0 {
|
|
return nil
|
|
}
|
|
return replacePersistedAgentAccountModelsWithTx(tx, account.ID, initialModels)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
asyncReportAIProviderInstall(provider)
|
|
return nil
|
|
}
|
|
|
|
func (a AgentService) UpdateAccount(req dto.AgentAccountUpdateReq) error {
|
|
agentAccountMutationMu.Lock()
|
|
defer agentAccountMutationMu.Unlock()
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if req.APIType != account.APIType {
|
|
return buserr.WithDetail("ErrInvalidParams", "API type cannot be changed", nil)
|
|
}
|
|
provider := account.Provider
|
|
if err := ensureAgentAccountNameAvailable(provider, req.Name, account.ID); err != nil {
|
|
return err
|
|
}
|
|
models, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
requestedVerifyModel := req.VerifyModel
|
|
if strings.TrimSpace(requestedVerifyModel) == "" {
|
|
requestedVerifyModel = account.VerifyModel
|
|
}
|
|
verifyModel, err := resolveAgentAccountVerifyModel(provider, requestedVerifyModel, models)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
validateAvailability := req.ValidateAvailability == nil || *req.ValidateAvailability
|
|
resolvedInput, err := resolveAgentAccountInput(provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, verifyModel, validateAvailability)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
account.Name = req.Name
|
|
account.APIKey = resolvedInput.APIKey
|
|
account.RememberAPIKey = req.RememberAPIKey
|
|
account.BaseURL = resolvedInput.BaseURL
|
|
account.APIType = resolvedInput.APIType
|
|
account.AuthMode = resolvedInput.AuthMode
|
|
account.VerifyModel = verifyModel
|
|
account.Remark = req.Remark
|
|
account.Verified = true
|
|
|
|
if err := global.DB.Save(account).Error; err != nil {
|
|
return err
|
|
}
|
|
terminalai.InvalidateTerminalRuntimeCache()
|
|
terminalai.InvalidateFileAIRuntimeCache()
|
|
if req.SyncAgents {
|
|
if err := a.syncAgentsByAccount(account); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a AgentService) PageAccounts(req dto.AgentAccountSearch) (int64, []dto.AgentAccountInfo, error) {
|
|
var opts []repo.DBOption
|
|
if strings.TrimSpace(req.Provider) != "" {
|
|
opts = append(opts, repo.WithByProvider(req.Provider))
|
|
}
|
|
if apiType := strings.TrimSpace(req.APIType); apiType != "" {
|
|
opts = append(opts, repo.WithByAPIType(apiType))
|
|
}
|
|
if req.TextOnly {
|
|
opts = append(opts, repo.WithTextAPIType())
|
|
}
|
|
if strings.TrimSpace(req.Name) != "" {
|
|
opts = append(opts, repo.WithByLikeName(req.Name))
|
|
}
|
|
count, list, err := agentAccountRepo.Page(req.Page, req.PageSize, opts...)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
items := make([]dto.AgentAccountInfo, 0, len(list))
|
|
for _, item := range list {
|
|
apiKey := ""
|
|
if item.RememberAPIKey {
|
|
apiKey = item.APIKey
|
|
}
|
|
items = append(items, dto.AgentAccountInfo{
|
|
ID: item.ID,
|
|
MasterAccountID: item.MasterAccountID,
|
|
Provider: item.Provider,
|
|
ProviderName: localizedAgentProviderName(item.Provider),
|
|
Name: item.Name,
|
|
APIKey: apiKey,
|
|
RememberAPIKey: item.RememberAPIKey,
|
|
BaseURL: item.BaseURL,
|
|
Models: nil,
|
|
APIType: item.APIType,
|
|
AuthMode: item.AuthMode,
|
|
VerifyModel: item.VerifyModel,
|
|
Verified: item.Verified,
|
|
Remark: item.Remark,
|
|
CreatedAt: item.CreatedAt,
|
|
})
|
|
}
|
|
if len(list) > 0 {
|
|
accountIDs := make([]uint, 0, len(list))
|
|
for _, account := range list {
|
|
accountIDs = append(accountIDs, account.ID)
|
|
}
|
|
var rows []model.AgentAccountModel
|
|
if err := global.DB.Where("account_id IN ?", accountIDs).Order("account_id ASC, sort_order ASC, id ASC").Find(&rows).Error; err != nil {
|
|
return 0, nil, err
|
|
}
|
|
modelsByAccount := make(map[uint][]dto.AgentAccountModel, len(list))
|
|
for _, row := range rows {
|
|
modelsByAccount[row.AccountID] = append(modelsByAccount[row.AccountID], dto.AgentAccountModel{
|
|
RecordID: row.ID,
|
|
ID: strings.TrimSpace(row.Model),
|
|
Name: strings.TrimSpace(row.Name),
|
|
})
|
|
}
|
|
for index, account := range list {
|
|
items[index].Models = modelsByAccount[account.ID]
|
|
}
|
|
}
|
|
return count, items, nil
|
|
}
|
|
|
|
func (a AgentService) CountAccountsByProviders(req dto.AgentAccountProviderCountReq) (map[string]int64, error) {
|
|
return agentAccountRepo.CountTextByProviders(req.Providers)
|
|
}
|
|
|
|
func (a AgentService) GetAccountModels(req dto.AgentAccountModelReq) ([]dto.AgentAccountModel, error) {
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return loadAgentAccountModels(account)
|
|
}
|
|
|
|
func (a AgentService) DiscoverAccountModels(req dto.AgentAccountModelDiscoverReq) ([]dto.AgentAccountModel, error) {
|
|
config, ok := providercatalog.FindAPIConfig(req.Provider, req.APIType)
|
|
if !ok || !config.DiscoverModels {
|
|
return nil, buserr.New("ErrAgentAccountModelsRequired")
|
|
}
|
|
baseURL, err := providercatalog.ResolveBaseURL(req.Provider, req.APIType, req.BaseURL)
|
|
if err != nil {
|
|
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
|
}
|
|
models, err := providercatalog.DiscoverModels(baseURL, req.APIKey)
|
|
if err != nil {
|
|
return nil, buserr.WithErr("ErrAgentAccountUnavailable", err)
|
|
}
|
|
return buildDiscoveredAgentAccountModels(models), nil
|
|
}
|
|
|
|
func (a AgentService) CreateAccountModel(req dto.AgentAccountModelCreateReq) error {
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
models, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, ok := findAgentAccountModelForProvider(account.Provider, models, nextModel.ID); ok {
|
|
return buserr.New("ErrRecordExist")
|
|
}
|
|
sortOrder := len(models) + 1
|
|
record := &model.AgentAccountModel{
|
|
AccountID: account.ID,
|
|
Model: nextModel.ID,
|
|
Name: nextModel.Name,
|
|
SortOrder: sortOrder,
|
|
}
|
|
if err := agentAccountModelRepo.Create(record); err != nil {
|
|
return err
|
|
}
|
|
return a.syncAgentsByAccount(account)
|
|
}
|
|
|
|
func (a AgentService) UpdateAccountModel(req dto.AgentAccountModelUpdateReq) error {
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
record, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.Model.RecordID), repo.WithByAccountID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
models, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nextModel, err := normalizeAgentAccountModel(account, req.Model)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nextModel.RecordID = req.Model.RecordID
|
|
for _, item := range models {
|
|
if item.RecordID == req.Model.RecordID {
|
|
continue
|
|
}
|
|
if sameProviderModelID(account.Provider, item.ID, nextModel.ID) {
|
|
return buserr.New("ErrRecordExist")
|
|
}
|
|
}
|
|
nextModels := make([]dto.AgentAccountModel, 0, len(models))
|
|
for _, item := range models {
|
|
if item.RecordID == req.Model.RecordID {
|
|
nextModels = append(nextModels, nextModel)
|
|
continue
|
|
}
|
|
nextModels = append(nextModels, item)
|
|
}
|
|
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
|
return err
|
|
}
|
|
previousModelID := record.Model
|
|
record.Model = nextModel.ID
|
|
record.Name = nextModel.Name
|
|
if err := agentAccountModelRepo.Save(record); err != nil {
|
|
return err
|
|
}
|
|
if sameProviderModelID(account.Provider, account.VerifyModel, previousModelID) {
|
|
account.VerifyModel = nextModel.ID
|
|
if err := agentAccountRepo.Save(account); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
terminalai.InvalidateTerminalRuntimeCache()
|
|
terminalai.InvalidateFileAIRuntimeCache()
|
|
return a.syncAgentsByAccount(account)
|
|
}
|
|
|
|
func (a AgentService) DeleteAccountModel(req dto.AgentAccountModelDeleteReq) error {
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
record, err := agentAccountModelRepo.GetFirst(repo.WithByID(req.RecordID), repo.WithByAccountID(req.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sameProviderModelID(account.Provider, account.VerifyModel, record.Model) {
|
|
return buserr.New("ErrAgentVerifyModelInUse")
|
|
}
|
|
models, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nextModels := make([]dto.AgentAccountModel, 0, len(models))
|
|
for _, item := range models {
|
|
if item.RecordID == req.RecordID {
|
|
continue
|
|
}
|
|
nextModels = append(nextModels, item)
|
|
}
|
|
if err := ensureAccountModelsNotBound(account, nextModels); err != nil {
|
|
return err
|
|
}
|
|
if err := agentAccountModelRepo.DeleteByID(req.RecordID); err != nil {
|
|
return err
|
|
}
|
|
if err := compactPersistedAgentAccountModelSortOrder(req.AccountID); err != nil {
|
|
return err
|
|
}
|
|
terminalai.InvalidateTerminalRuntimeCache()
|
|
terminalai.InvalidateFileAIRuntimeCache()
|
|
return a.syncAgentsByAccount(account)
|
|
}
|
|
|
|
func (a AgentService) SyncAgentsByAccount(account *model.AgentAccount) error {
|
|
if account == nil || account.ID == 0 {
|
|
return nil
|
|
}
|
|
return a.syncAgentsByAccount(account)
|
|
}
|
|
|
|
func (a AgentService) VerifyAccount(req dto.AgentAccountVerifyReq) error {
|
|
_, err := resolveAgentAccountInput(req.Provider, req.APIType, req.AuthMode, req.APIKey, req.BaseURL, req.Model, true)
|
|
return err
|
|
}
|
|
|
|
func (a AgentService) DeleteAccount(req dto.AgentAccountDeleteReq) error {
|
|
agentAccountMutationMu.Lock()
|
|
defer agentAccountMutationMu.Unlock()
|
|
if err := global.DB.Transaction(func(tx *gorm.DB) error {
|
|
var agentCount int64
|
|
if err := tx.Model(&model.Agent{}).Where("account_id = ?", req.ID).Count(&agentCount).Error; err != nil {
|
|
return err
|
|
}
|
|
if agentCount > 0 {
|
|
return buserr.New("ErrAgentAccountBound")
|
|
}
|
|
used, err := agentAccountUsedBySetting(tx, req.ID, "AIStatus", "AIAccountID")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if used {
|
|
return buserr.New("ErrTerminalAIAccountInUse")
|
|
}
|
|
used, err = agentAccountUsedBySetting(tx, req.ID, "FileAIStatus", "FileAIAccountID")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if used {
|
|
return buserr.New("ErrFileAIAccountInUse")
|
|
}
|
|
if err := tx.Where("account_id = ?", req.ID).Delete(&model.AgentAccountModel{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Delete(&model.AgentAccount{}, req.ID).Error
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
terminalai.InvalidateTerminalRuntimeCache()
|
|
terminalai.InvalidateFileAIRuntimeCache()
|
|
return nil
|
|
}
|
|
|
|
func (a AgentService) GetSecurityConfig(req dto.AgentIDReq) (*dto.AgentSecurityConfig, error) {
|
|
agent, _, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := extractSecurityConfig(conf)
|
|
return &result, nil
|
|
}
|
|
|
|
func (a AgentService) UpdateSecurityConfig(req dto.AgentSecurityConfigUpdateReq) error {
|
|
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
allowedOrigins, err := normalizeAllowedOrigins(req.AllowedOrigins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(allowedOrigins) == 0 {
|
|
return fmt.Errorf("allowed origins is required")
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setSecurityConfig(conf, dto.AgentSecurityConfig{AllowedOrigins: allowedOrigins})
|
|
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
|
|
return err
|
|
}
|
|
if err := syncOpenclawAllowedOriginEnv(install, allowedOrigins); err != nil {
|
|
return err
|
|
}
|
|
return appInstallRepo.Save(context.Background(), install)
|
|
}
|
|
|
|
func (a AgentService) GetOtherConfig(req dto.AgentIDReq) (*dto.AgentOtherConfig, error) {
|
|
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if agent.AgentType == constant.AppHermesAgent {
|
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
return &dto.AgentOtherConfig{
|
|
UserTimezone: cfg.Timezone,
|
|
BrowserEnabled: true,
|
|
NPMRegistry: "https://registry.npmjs.org/",
|
|
DashboardUsername: auth.Username,
|
|
DashboardPassword: auth.Password,
|
|
}, nil
|
|
}
|
|
if agent.AgentType == constant.AppCopaw {
|
|
auth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
return &dto.AgentOtherConfig{
|
|
DashboardUsername: auth.Username,
|
|
DashboardPassword: auth.Password,
|
|
}, nil
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := extractOtherConfig(conf)
|
|
npmRegistry, err := getOpenclawNPMRegistry(install.ContainerName)
|
|
if err == nil {
|
|
result.NPMRegistry = npmRegistry
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
func (a AgentService) UpdateOtherConfig(req dto.AgentOtherConfigUpdateReq) error {
|
|
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if agent.AgentType == constant.AppHermesAgent {
|
|
if strings.TrimSpace(req.UserTimezone) == "" {
|
|
return buserr.New("ErrInvalidParams")
|
|
}
|
|
account, err := agentAccountRepo.GetFirst(repo.WithByID(agent.AccountID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
previousAuth := readAgentDashboardAuthFromInstall(install, agent.AgentType)
|
|
nextAuth := normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword)
|
|
if err := writeHermesConfig(path.Dir(agent.ConfigPath), account, agent.Model, strings.TrimSpace(req.UserTimezone)); err != nil {
|
|
return err
|
|
}
|
|
if err := writeAgentDashboardAuthEnv(install.GetEnvPath(), agent.AgentType, nextAuth, true); err != nil {
|
|
return err
|
|
}
|
|
operate := constant.Restart
|
|
if previousAuth.Username != nextAuth.Username || previousAuth.Password != nextAuth.Password {
|
|
operate = constant.Rebuild
|
|
}
|
|
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
|
InstallId: install.ID,
|
|
Operate: operate,
|
|
})
|
|
}
|
|
if agent.AgentType == constant.AppCopaw {
|
|
return updateQwenPawDashboardAuth(install, normalizeAgentDashboardAuth(req.DashboardUsername, req.DashboardPassword))
|
|
}
|
|
if strings.TrimSpace(req.UserTimezone) == "" || strings.TrimSpace(req.NPMRegistry) == "" {
|
|
return buserr.New("ErrInvalidParams")
|
|
}
|
|
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
|
return err
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setOtherConfig(conf, dto.AgentOtherConfig{
|
|
UserTimezone: strings.TrimSpace(req.UserTimezone),
|
|
BrowserEnabled: req.BrowserEnabled,
|
|
})
|
|
if err := writeOpenclawConfigRaw(agent.ConfigPath, conf); err != nil {
|
|
return err
|
|
}
|
|
if err := setOpenclawNPMRegistry(install.ContainerName, req.NPMRegistry); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a AgentService) GetConfigFile(req dto.AgentConfigFileReq) (*dto.AgentConfigFile, error) {
|
|
agent, _, err := a.loadAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
content, err := os.ReadFile(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto.AgentConfigFile{Content: string(content)}, nil
|
|
}
|
|
|
|
func (a AgentService) UpdateConfigFile(req dto.AgentConfigFileUpdateReq) error {
|
|
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := validateAgentConfigFileContent(agent.AgentType, req.Content); err != nil {
|
|
return err
|
|
}
|
|
info, err := os.Stat(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(agent.ConfigPath, []byte(req.Content), info.Mode()); err != nil {
|
|
return err
|
|
}
|
|
if agent.AgentType == constant.AppHermesAgent {
|
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cfg.Model.Default != "" {
|
|
agent.Model = cfg.Model.Default
|
|
if err := agentRepo.Save(agent); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return NewIAppInstalledService().Operate(request.AppInstalledOperate{
|
|
InstallId: install.ID,
|
|
Operate: constant.Restart,
|
|
})
|
|
}
|
|
|
|
func validateAgentConfigFileContent(agentType, content string) error {
|
|
var payload interface{}
|
|
if agentType == constant.AppHermesAgent {
|
|
return yaml.Unmarshal([]byte(content), &payload)
|
|
}
|
|
return json.Unmarshal([]byte(content), &payload)
|
|
}
|
|
|
|
func getOpenclawNPMRegistry(containerName string) (string, error) {
|
|
registry, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
registry = strings.TrimSpace(registry)
|
|
if registry == "" {
|
|
return defaultOpenclawNPMRegistry, nil
|
|
}
|
|
return registry, nil
|
|
}
|
|
|
|
func setOpenclawNPMRegistry(containerName, registry string) error {
|
|
return cmd.NewCommandMgr().Run("docker", "exec", containerName, "npm", "set", "registry", registry)
|
|
}
|
|
|
|
func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
|
|
agent, err := agentRepo.GetFirst(repo.WithByID(agentID))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if agent.AppInstallID == 0 {
|
|
return nil, nil, buserr.New("ErrRecordNotFound")
|
|
}
|
|
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return agent, &install, nil
|
|
}
|
|
|
|
func (a AgentService) loadOpenclawAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) {
|
|
agent, install, err := a.loadAgentAndInstall(agentID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if agent.AgentType != constant.AppOpenclaw {
|
|
return nil, nil, fmt.Errorf("%s does not support", agent.AgentType)
|
|
}
|
|
return agent, install, nil
|
|
}
|
|
|
|
func (a AgentService) loadAgentConfig(agentID uint) (*model.Agent, *model.AppInstall, map[string]interface{}, error) {
|
|
agent, install, err := a.loadAgentAndInstall(agentID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return agent, install, conf, nil
|
|
}
|
|
|
|
func (a AgentService) loadOpenclawAgentConfig(agentID uint) (*model.Agent, *model.AppInstall, map[string]interface{}, error) {
|
|
agent, install, conf, err := a.loadAgentConfig(agentID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
if agent.AgentType != constant.AppOpenclaw {
|
|
return nil, nil, nil, fmt.Errorf("%s does not support", agent.AgentType)
|
|
}
|
|
return agent, install, conf, nil
|
|
}
|
|
|
|
func (a AgentService) mutateAgentConfig(agentID uint, fn func(agent *model.Agent, install *model.AppInstall, conf map[string]interface{}) error) error {
|
|
agent, install, conf, err := a.loadAgentConfig(agentID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := fn(agent, install, conf); err != nil {
|
|
return err
|
|
}
|
|
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
|
}
|
|
|
|
func (a AgentService) syncAgentsByAccount(account *model.AgentAccount) error {
|
|
agents, err := agentRepo.List(repo.WithByAccountID(account.ID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
accountModels, err := loadAgentAccountModels(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(accountModels) == 0 {
|
|
return nil
|
|
}
|
|
for _, agent := range agents {
|
|
selectedAccountModel, err := requireAgentAccountModelForProvider(account.Provider, accountModels, agent.Model)
|
|
if err != nil {
|
|
return buserr.WithName("ErrAgentModelInUse", agent.Name)
|
|
}
|
|
resolvedRuntime, err := buildOpenclawAccountModelRuntime(account, selectedAccountModel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
modelName := resolvedRuntime.StoredModel
|
|
apiType := resolvedRuntime.APIType
|
|
confDir := path.Dir(agent.ConfigPath)
|
|
switch agent.AgentType {
|
|
case constant.AppOpenclaw:
|
|
conf, err := readOpenclawConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fallbacks := extractOpenclawFallbackModelIDs(conf, account, accountModels, selectedAccountModel.ID)
|
|
if err := writeOpenclawConfig(confDir, account, modelName, agent.Token, nil, fallbacks); err != nil {
|
|
return err
|
|
}
|
|
case constant.AppHermesAgent:
|
|
cfg, err := readHermesConfig(agent.ConfigPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeHermesConfig(confDir, account, modelName, cfg.Timezone); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
agent.BaseURL = account.BaseURL
|
|
agent.APIKey = account.APIKey
|
|
agent.Provider = account.Provider
|
|
agent.Model = modelName
|
|
agent.APIType = apiType
|
|
_ = agentRepo.Save(&agent)
|
|
}
|
|
return nil
|
|
}
|