mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d78ca5a86 | ||
|
|
a9b65b7ce5 | ||
|
|
291b40a45d | ||
|
|
a959da7d44 | ||
|
|
26c4e8323f | ||
|
|
0534badce7 | ||
|
|
594d818db0 | ||
|
|
04dceb93a7 | ||
|
|
7b554c0d61 | ||
|
|
bb642cd8a8 | ||
|
|
40249b527d | ||
|
|
74ff7ddc19 | ||
|
|
096ec8edc7 | ||
|
|
0d19069059 | ||
|
|
0da833302b | ||
|
|
4a69cf0ff5 | ||
|
|
39704ef4e5 | ||
|
|
e52cfeeb78 | ||
|
|
476429005c | ||
|
|
6fb41dc711 | ||
|
|
2cb2058cfa | ||
|
|
8533dd1d33 | ||
|
|
0cfbcd25fc | ||
|
|
b91ca8bdb3 | ||
|
|
72764863bc | ||
|
|
a7b9fd0181 | ||
|
|
2b3164fe82 | ||
|
|
2c6307049a | ||
|
|
acc1b9270f | ||
|
|
60e4143525 | ||
|
|
99bcd6d059 | ||
|
|
c4259dd004 | ||
|
|
1c5a4bc978 | ||
|
|
42f6e9a9a6 | ||
|
|
569363530a | ||
|
|
ec9eba0b9a | ||
|
|
5376d54089 | ||
|
|
c10e5747b0 | ||
|
|
5a80931a49 | ||
|
|
f541379daa | ||
|
|
0018335d64 | ||
|
|
df90286253 | ||
|
|
8dfa7945d7 | ||
|
|
1f4292d9cf | ||
|
|
e635a7beba | ||
|
|
59c657e403 | ||
|
|
ff8f79ed52 | ||
|
|
57a7edf481 | ||
|
|
bbd33c66f1 | ||
|
|
b05ed1a0ca | ||
|
|
b645c4edb4 | ||
|
|
9f61aac8be | ||
|
|
11d78644ee | ||
|
|
b96428e1d6 | ||
|
|
6432c35b1a | ||
|
|
fa843f0940 | ||
|
|
5c7ffc561b | ||
|
|
c2f0c8c6ac | ||
|
|
5bf6d17451 | ||
|
|
7f799004c3 | ||
|
|
c68251cada | ||
|
|
23e7d03d8d | ||
|
|
5085db7a2f | ||
|
|
a9cf7a7d1d | ||
|
|
cf18995182 | ||
|
|
24d7dc0786 |
+296
-10
@@ -111,6 +111,27 @@ func (b *BaseApi) UpdateAgentModelConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent overview
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentOverviewReq true "request"
|
||||
// @Success 200 {object} dto.AgentOverview
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/overview [post]
|
||||
func (b *BaseApi) GetAgentOverview(c *gin.Context) {
|
||||
var req dto.AgentOverviewReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
res, err := agentService.GetOverview(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Providers
|
||||
// @Success 200 {array} dto.ProviderInfo
|
||||
@@ -190,6 +211,87 @@ func (b *BaseApi) PageAgentAccounts(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List Agent account models
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelReq true "request"
|
||||
// @Success 200 {array} dto.AgentAccountModel
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models [post]
|
||||
func (b *BaseApi) GetAgentAccountModels(c *gin.Context) {
|
||||
var req dto.AgentAccountModelReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
list, err := agentService.GetAccountModels(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, list)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Create Agent account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelCreateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/create [post]
|
||||
func (b *BaseApi) CreateAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelCreateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.CreateAccountModel(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/update [post]
|
||||
func (b *BaseApi) UpdateAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateAccountModel(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Delete Agent account model
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentAccountModelDeleteReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/accounts/models/delete [post]
|
||||
func (b *BaseApi) DeleteAgentAccountModel(c *gin.Context) {
|
||||
var req dto.AgentAccountModelDeleteReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.DeleteAccountModel(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Verify Agent account
|
||||
// @Accept json
|
||||
@@ -315,13 +417,13 @@ func (b *BaseApi) UpdateAgentTelegramConfig(c *gin.Context) {
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Discord channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentDiscordConfigReq true "request"
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentDiscordConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/discord/get [post]
|
||||
func (b *BaseApi) GetAgentDiscordConfig(c *gin.Context) {
|
||||
var req dto.AgentDiscordConfigReq
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -356,13 +458,13 @@ func (b *BaseApi) UpdateAgentDiscordConfig(c *gin.Context) {
|
||||
// @Tags AI
|
||||
// @Summary Get Agent QQ Bot channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentWecomConfigReq true "request"
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentWecomConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/wecom/get [post]
|
||||
func (b *BaseApi) GetAgentWecomConfig(c *gin.Context) {
|
||||
var req dto.AgentWecomConfigReq
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -394,16 +496,57 @@ func (b *BaseApi) UpdateAgentWecomConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent DingTalk channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentDingTalkConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/dingtalk/get [post]
|
||||
func (b *BaseApi) GetAgentDingTalkConfig(c *gin.Context) {
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetDingTalkConfig(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent DingTalk channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentDingTalkConfigUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/dingtalk/update [post]
|
||||
func (b *BaseApi) UpdateAgentDingTalkConfig(c *gin.Context) {
|
||||
var req dto.AgentDingTalkConfigUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateDingTalkConfig(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent QQ Bot channel config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentQQBotConfigReq true "request"
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentQQBotConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/qqbot/get [post]
|
||||
func (b *BaseApi) GetAgentQQBotConfig(c *gin.Context) {
|
||||
var req dto.AgentQQBotConfigReq
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -479,13 +622,13 @@ func (b *BaseApi) CheckAgentPlugin(c *gin.Context) {
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Security config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentSecurityConfigReq true "request"
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentSecurityConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/security/get [post]
|
||||
func (b *BaseApi) GetAgentSecurityConfig(c *gin.Context) {
|
||||
var req dto.AgentSecurityConfigReq
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -520,13 +663,13 @@ func (b *BaseApi) UpdateAgentSecurityConfig(c *gin.Context) {
|
||||
// @Tags AI
|
||||
// @Summary Get Agent Other config
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentOtherConfigReq true "request"
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {object} dto.AgentOtherConfig
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/other/get [post]
|
||||
func (b *BaseApi) GetAgentOtherConfig(c *gin.Context) {
|
||||
var req dto.AgentOtherConfigReq
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -558,6 +701,149 @@ func (b *BaseApi) UpdateAgentOtherConfig(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Get Agent config file
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentConfigFileReq true "request"
|
||||
// @Success 200 {object} dto.AgentConfigFile
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/config-file/get [post]
|
||||
func (b *BaseApi) GetAgentConfigFile(c *gin.Context) {
|
||||
var req dto.AgentConfigFileReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.GetConfigFile(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent config file
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentConfigFileUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/config-file/update [post]
|
||||
func (b *BaseApi) UpdateAgentConfigFile(c *gin.Context) {
|
||||
var req dto.AgentConfigFileUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateConfigFile(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary List Agent skills
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentIDReq true "request"
|
||||
// @Success 200 {array} dto.AgentSkillItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/skills/list [post]
|
||||
func (b *BaseApi) ListAgentSkills(c *gin.Context) {
|
||||
var req dto.AgentIDReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.ListSkills(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Search Agent skills
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentSkillSearchReq true "request"
|
||||
// @Success 200 {array} dto.AgentSkillSearchItem
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/skills/search [post]
|
||||
func (b *BaseApi) SearchAgentSkills(c *gin.Context) {
|
||||
var req dto.AgentSkillSearchReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
data, err := agentService.SearchSkills(req)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Update Agent skill status
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentSkillUpdateReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/skills/update [post]
|
||||
func (b *BaseApi) UpdateAgentSkill(c *gin.Context) {
|
||||
var req dto.AgentSkillUpdateReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.UpdateSkill(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Install Agent skill
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentSkillInstallReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/skills/install [post]
|
||||
func (b *BaseApi) InstallAgentSkill(c *gin.Context) {
|
||||
var req dto.AgentSkillInstallReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.InstallSkill(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Login Agent Weixin channel
|
||||
// @Accept json
|
||||
// @Param request body dto.AgentWeixinLoginReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /ai/agents/channel/weixin/login [post]
|
||||
func (b *BaseApi) LoginAgentWeixinChannel(c *gin.Context) {
|
||||
var req dto.AgentWeixinLoginReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := agentService.LoginWeixinChannel(req); err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Approve Agent channel pairing code
|
||||
// @Accept json
|
||||
|
||||
@@ -70,6 +70,7 @@ var (
|
||||
|
||||
recycleBinService = service.NewIRecycleBinService()
|
||||
favoriteService = service.NewIFavoriteService()
|
||||
hostService = service.NewIHostService()
|
||||
|
||||
websiteCAService = service.NewIWebsiteCAService()
|
||||
taskService = service.NewITaskService()
|
||||
|
||||
@@ -515,6 +515,29 @@ func (b *BaseApi) WgetFile(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Stop wget file download
|
||||
// @Accept json
|
||||
// @Param request body request.FileProcessReq true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /files/wget/stop [post]
|
||||
// @x-panel-log {"bodyKeys":["key"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"停止下载任务 [key]","formatEN":"Stop wget task [key]"}
|
||||
func (b *BaseApi) StopWget(c *gin.Context) {
|
||||
var req request.FileProcessReq
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Key) == "" {
|
||||
helper.BadRequest(c, errors.New("key is required"))
|
||||
return
|
||||
}
|
||||
|
||||
files.CancelDownload(req.Key)
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags File
|
||||
// @Summary Move file
|
||||
// @Accept json
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host, err := hostService.Create(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, host)
|
||||
}
|
||||
|
||||
func (b *BaseApi) TestByInfo(c *gin.Context) {
|
||||
var req dto.HostConnTest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, hostService.TestByInfo(req))
|
||||
}
|
||||
|
||||
func (b *BaseApi) TestByID(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, hostService.TestLocalConn(req.ID))
|
||||
}
|
||||
|
||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
var req dto.SearchForTree
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := hostService.SearchForTree(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
var req dto.SearchPageWithGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := hostService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{Items: list, Total: total})
|
||||
}
|
||||
|
||||
func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||
var req dto.OperateByIDs
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := hostService.Delete(req.IDs); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(req.Password) != 0 && req.AuthMode == "password" {
|
||||
req.Password, err = hostService.EncryptHost(req.Password)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
req.PrivateKey = ""
|
||||
req.PassPhrase = ""
|
||||
}
|
||||
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
|
||||
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if len(req.PassPhrase) != 0 {
|
||||
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
req.Password = ""
|
||||
}
|
||||
|
||||
upMap := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"group_id": req.GroupID,
|
||||
"addr": req.Addr,
|
||||
"port": req.Port,
|
||||
"user": req.User,
|
||||
"auth_mode": req.AuthMode,
|
||||
"remember_password": req.RememberPassword,
|
||||
"description": req.Description,
|
||||
}
|
||||
if req.AuthMode == "password" {
|
||||
upMap["password"] = req.Password
|
||||
upMap["private_key"] = ""
|
||||
upMap["pass_phrase"] = ""
|
||||
} else {
|
||||
upMap["password"] = ""
|
||||
upMap["private_key"] = req.PrivateKey
|
||||
upMap["pass_phrase"] = req.PassPhrase
|
||||
}
|
||||
hostItem, err := hostService.Update(req.ID, upMap)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, hostItem)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
|
||||
var req dto.ChangeGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := hostService.Update(req.ID, map[string]interface{}{"group_id": req.GroupID}); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetHostByID(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := hostService.GetHostByID(req.ID)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
@@ -27,6 +27,15 @@ func (b *BaseApi) GetSettingInfo(c *gin.Context) {
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetTerminalAISettingInfo(c *gin.Context) {
|
||||
setting, err := settingService.GetTerminalAIInfo()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, setting)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load system available status
|
||||
// @Success 200
|
||||
@@ -59,6 +68,19 @@ func (b *BaseApi) UpdateSetting(c *gin.Context) {
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
func (b *BaseApi) UpdateTerminalAISetting(c *gin.Context) {
|
||||
var req dto.TerminalAIInfo
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := settingService.UpdateTerminalAI(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags System Setting
|
||||
// @Summary Load local backup dir
|
||||
// @Success 200 {string} path
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/terminal"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -40,9 +42,33 @@ func (b *BaseApi) WsSSH(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
client, err := loadLocalConn()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
hostID, _ := strconv.Atoi(c.DefaultQuery("id", "0"))
|
||||
var client *ssh.SSHClient
|
||||
if hostID > 0 {
|
||||
host, err := service.GetHostInfo(uint(hostID))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
|
||||
return
|
||||
}
|
||||
connInfo := ssh.ConnInfo{
|
||||
Addr: host.Addr,
|
||||
Port: int(host.Port),
|
||||
User: host.User,
|
||||
AuthMode: host.AuthMode,
|
||||
Password: host.Password,
|
||||
PrivateKey: []byte(host.PrivateKey),
|
||||
}
|
||||
if len(host.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
client, err = ssh.NewClient(connInfo)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
client, err = loadLocalConn()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
defer client.Close()
|
||||
command := c.DefaultQuery("command", "")
|
||||
|
||||
+159
-58
@@ -8,15 +8,9 @@ type AgentCreateReq struct {
|
||||
WebUIPort int `json:"webUIPort" validate:"required"`
|
||||
BridgePort int `json:"bridgePort"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
AgentType string `json:"agentType"`
|
||||
Provider string `json:"provider"`
|
||||
AgentType string `json:"agentType" validate:"required,oneof=openclaw copaw"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
AccountID uint `json:"accountId"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Token string `json:"token"`
|
||||
TaskID string `json:"taskID"`
|
||||
Advanced bool `json:"advanced"`
|
||||
@@ -75,17 +69,66 @@ type AgentModelConfigUpdateReq struct {
|
||||
Model string `json:"model" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOverviewReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentIDReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOverview struct {
|
||||
Snapshot AgentOverviewSnapshot `json:"snapshot"`
|
||||
}
|
||||
|
||||
type AgentOverviewSnapshot struct {
|
||||
ContainerStatus string `json:"containerStatus"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
DefaultModel string `json:"defaultModel"`
|
||||
ChannelCount int `json:"channelCount"`
|
||||
SkillCount int `json:"skillCount"`
|
||||
JobCount int `json:"jobCount"`
|
||||
SessionCount int `json:"sessionCount"`
|
||||
}
|
||||
|
||||
type AgentAccountModel struct {
|
||||
RecordID uint `json:"recordId"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input []string `json:"input"`
|
||||
}
|
||||
|
||||
type AgentAccountModelReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountModelCreateReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model AgentAccountModel `json:"model" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountModelUpdateReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
Model AgentAccountModel `json:"model" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountModelDeleteReq struct {
|
||||
AccountID uint `json:"accountId" validate:"required"`
|
||||
RecordID uint `json:"recordId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentAccountCreateReq struct {
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Remark string `json:"remark"`
|
||||
Provider string `json:"provider" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type AgentAccountUpdateReq struct {
|
||||
@@ -94,10 +137,7 @@ type AgentAccountUpdateReq struct {
|
||||
APIKey string `json:"apiKey" validate:"required"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseURL"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
APIType string `json:"apiType" validate:"required"`
|
||||
Remark string `json:"remark"`
|
||||
SyncAgents bool `json:"syncAgents"`
|
||||
}
|
||||
@@ -119,25 +159,27 @@ type AgentAccountSearch struct {
|
||||
}
|
||||
|
||||
type AgentAccountInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ID uint `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
ProviderName string `json:"providerName"`
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Models []AgentAccountModel `json:"models"`
|
||||
APIType string `json:"apiType"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type ProviderModelInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input []string `json:"input"`
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
@@ -195,14 +237,10 @@ type AgentTelegramConfig struct {
|
||||
|
||||
type AgentChannelPairingApproveReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom"`
|
||||
Type string `json:"type" validate:"required,oneof=feishu telegram discord wecom dingtalk-connector"`
|
||||
PairingCode string `json:"pairingCode" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentWecomConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentWecomConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
@@ -219,8 +257,31 @@ type AgentWecomConfig struct {
|
||||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
type AgentQQBotConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
type AgentDingTalkConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ClientID string `json:"clientId" validate:"required"`
|
||||
ClientSecret string `json:"clientSecret" validate:"required"`
|
||||
DmPolicy string `json:"dmPolicy" validate:"required,oneof=pairing allowlist open disabled"`
|
||||
AllowFrom []string `json:"allowFrom"`
|
||||
GroupPolicy string `json:"groupPolicy" validate:"required,oneof=open allowlist disabled"`
|
||||
GroupAllowFrom []string `json:"groupAllowFrom"`
|
||||
}
|
||||
|
||||
type AgentDingTalkConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ClientID string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
DmPolicy string `json:"dmPolicy"`
|
||||
AllowFrom []string `json:"allowFrom"`
|
||||
GroupPolicy string `json:"groupPolicy"`
|
||||
GroupAllowFrom []string `json:"groupAllowFrom"`
|
||||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
type AgentWeixinLoginReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentQQBotConfigUpdateReq struct {
|
||||
@@ -239,23 +300,19 @@ type AgentQQBotConfig struct {
|
||||
|
||||
type AgentPluginInstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=qqbot wecom"`
|
||||
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentPluginCheckReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=qqbot wecom"`
|
||||
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"`
|
||||
}
|
||||
|
||||
type AgentPluginStatus struct {
|
||||
Installed bool `json:"installed"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentDiscordConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
@@ -273,10 +330,6 @@ type AgentDiscordConfig struct {
|
||||
Proxy string `json:"proxy"`
|
||||
}
|
||||
|
||||
type AgentSecurityConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentSecurityConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
@@ -286,17 +339,65 @@ type AgentSecurityConfig struct {
|
||||
AllowedOrigins []string `json:"allowedOrigins"`
|
||||
}
|
||||
|
||||
type AgentOtherConfigReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOtherConfigUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
UserTimezone string `json:"userTimezone" validate:"required"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentOtherConfig struct {
|
||||
UserTimezone string `json:"userTimezone"`
|
||||
BrowserEnabled bool `json:"browserEnabled"`
|
||||
NPMRegistry string `json:"npmRegistry"`
|
||||
}
|
||||
|
||||
type AgentConfigFileReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentConfigFileUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentConfigFile struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type AgentSkillSearchReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
|
||||
Keyword string `json:"keyword" validate:"required"`
|
||||
}
|
||||
|
||||
type AgentSkillItem struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Source string `json:"source"`
|
||||
Bundled bool `json:"bundled"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type AgentSkillSearchItem struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Summary string `json:"summary"`
|
||||
Version string `json:"version"`
|
||||
Source string `json:"source"`
|
||||
Score string `json:"score"`
|
||||
}
|
||||
|
||||
type AgentSkillUpdateReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type AgentSkillInstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
|
||||
Slug string `json:"slug" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@ type SearchPageWithType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type SearchPageWithGroup struct {
|
||||
PageInfo
|
||||
GroupID uint `json:"groupID"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" validate:"required,number"`
|
||||
PageSize int `json:"pageSize" validate:"required,number"`
|
||||
|
||||
+11
-10
@@ -287,16 +287,17 @@ type BatchDelete struct {
|
||||
}
|
||||
|
||||
type ComposeInfo struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
ContainerCount int `json:"containerCount"`
|
||||
RunningCount int `json:"runningCount"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
Workdir string `json:"workdir"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env string `json:"env"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
ContainerCount int `json:"containerCount"`
|
||||
RunningCount int `json:"runningCount"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
Workdir string `json:"workdir"`
|
||||
ComposeFileExists bool `json:"composeFileExists"`
|
||||
Path string `json:"path"`
|
||||
Containers []ComposeContainer `json:"containers"`
|
||||
Env string `json:"env"`
|
||||
}
|
||||
type ComposeContainer struct {
|
||||
ContainerID string `json:"containerID"`
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
import "time"
|
||||
|
||||
type HostOperate struct {
|
||||
ID uint `json:"id"`
|
||||
@@ -34,11 +32,6 @@ type SearchForTree struct {
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
type ChangeHostGroup struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
GroupID uint `json:"groupID" validate:"required"`
|
||||
}
|
||||
|
||||
type HostInfo struct {
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
@@ -269,6 +269,7 @@ type WebsiteProxyConfig struct {
|
||||
Replaces map[string]string `json:"replaces"`
|
||||
SNI bool `json:"sni"`
|
||||
ProxySSLName string `json:"proxySSLName"`
|
||||
SSLVerify bool `json:"sslVerify"`
|
||||
CorsConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,14 @@ type SystemProxy struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type TerminalAIInfo struct {
|
||||
AIStatus string `json:"aiStatus"`
|
||||
AIAccountID string `json:"aiAccountId"`
|
||||
AIPrefix string `json:"aiPrefix" validate:"required,oneof=@ai #ai /ai"`
|
||||
AIRiskCommands string `json:"aiRiskCommands"`
|
||||
AIRiskCommandsDefault string `json:"aiRiskCommandsDefault"`
|
||||
}
|
||||
|
||||
type CommonDescription struct {
|
||||
ID string `json:"id" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
|
||||
@@ -6,10 +6,7 @@ type AgentAccount struct {
|
||||
Name string `json:"name"`
|
||||
APIKey string `json:"apiKey"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Model string `json:"model"`
|
||||
APIType string `json:"apiType"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
RememberAPIKey bool `json:"rememberApiKey"`
|
||||
Verified bool `json:"verified"`
|
||||
Remark string `json:"remark"`
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
type AgentAccountModel struct {
|
||||
BaseModel
|
||||
AccountID uint `json:"accountId" gorm:"index"`
|
||||
Model string `json:"model" gorm:"index"`
|
||||
Name string `json:"name"`
|
||||
ContextWindow int `json:"contextWindow"`
|
||||
MaxTokens int `json:"maxTokens"`
|
||||
Reasoning bool `json:"reasoning"`
|
||||
Input string `json:"input" gorm:"type:text"`
|
||||
SortOrder int `json:"sortOrder" gorm:"index"`
|
||||
}
|
||||
|
||||
func (AgentAccountModel) TableName() string {
|
||||
return "agent_account_models"
|
||||
}
|
||||
+230
-60
@@ -5,8 +5,19 @@ import (
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
ID string
|
||||
Name string
|
||||
ID string
|
||||
Name string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
Reasoning bool
|
||||
Input []string
|
||||
}
|
||||
|
||||
type RuntimeDefault struct {
|
||||
APIType string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
Input []string
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
@@ -15,8 +26,8 @@ type Meta struct {
|
||||
Sort uint
|
||||
DefaultBaseURL string
|
||||
EnvKey string
|
||||
Default RuntimeDefault
|
||||
Models []Model
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
var catalog = map[string]Meta{
|
||||
@@ -26,14 +37,24 @@ var catalog = map[string]Meta{
|
||||
Sort: 10,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "CUSTOM_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{},
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
},
|
||||
"ollama": {
|
||||
Key: "ollama",
|
||||
DisplayName: "Ollama",
|
||||
Sort: 15,
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-responses",
|
||||
ContextWindow: 160000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
},
|
||||
"vllm": {
|
||||
Key: "vllm",
|
||||
@@ -41,8 +62,13 @@ var catalog = map[string]Meta{
|
||||
Sort: 20,
|
||||
DefaultBaseURL: "",
|
||||
EnvKey: "VLLM_API_KEY",
|
||||
Enabled: true,
|
||||
Models: []Model{},
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{},
|
||||
},
|
||||
"deepseek": {
|
||||
Key: "deepseek",
|
||||
@@ -50,11 +76,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 25,
|
||||
DefaultBaseURL: "https://api.deepseek.com/v1",
|
||||
EnvKey: "DEEPSEEK_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "deepseek/deepseek-chat", Name: "DeepSeek Chat"},
|
||||
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner"},
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B"},
|
||||
{ID: "deepseek/deepseek-reasoner", Name: "DeepSeek Reasoner", Reasoning: true},
|
||||
{ID: "deepseek/deepseek-r1:1.5b", Name: "DeepSeek R1 1.5B", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"bailian-coding-plan": {
|
||||
@@ -63,16 +94,21 @@ var catalog = map[string]Meta{
|
||||
Sort: 30,
|
||||
DefaultBaseURL: "https://coding.dashscope.aliyuncs.com/v1",
|
||||
EnvKey: "QWEN_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus"},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next"},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus"},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5"},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5"},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "bailian-coding-plan/qwen3.5-plus", Name: "Qwen3.5-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-max", Name: "Qwen3-Max", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-next", Name: "Qwen3-Coder-Next", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/qwen3-coder-plus", Name: "Qwen3-Coder-Plus", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/minimax-m2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/kimi-k2.5", Name: "Kimi-k2.5", Reasoning: true},
|
||||
{ID: "bailian-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"ark-coding-plan": {
|
||||
@@ -81,14 +117,19 @@ var catalog = map[string]Meta{
|
||||
Sort: 35,
|
||||
DefaultBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
||||
EnvKey: "ARK_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "ark-coding-plan/doubao-seed-2.0-code", Name: "Doubao-Seed-2.0-Code"},
|
||||
{ID: "ark-coding-plan/doubao-seed-code", Name: "Doubao-Seed-Code"},
|
||||
{ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi-K2.5"},
|
||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "ark-coding-plan/deepseek-v3.2", Name: "DeepSeek-V3.2"},
|
||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi-K2-thinking"},
|
||||
{ID: "ark-coding-plan/kimi-k2.5", Name: "Kimi-K2.5", Reasoning: true},
|
||||
{ID: "ark-coding-plan/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
{ID: "ark-coding-plan/deepseek-v3.2", Name: "DeepSeek-V3.2", Reasoning: true},
|
||||
{ID: "ark-coding-plan/kimi-k2-thinking", Name: "Kimi-K2-thinking", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"zai": {
|
||||
@@ -97,12 +138,17 @@ var catalog = map[string]Meta{
|
||||
Sort: 40,
|
||||
DefaultBaseURL: "https://open.bigmodel.cn/api/paas/v4",
|
||||
EnvKey: "ZAI_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 204800,
|
||||
MaxTokens: 131072,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "zai/glm-5", Name: "GLM-5"},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7"},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash"},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX"},
|
||||
{ID: "zai/glm-5", Name: "GLM-5", Reasoning: true},
|
||||
{ID: "zai/glm-4.7", Name: "GLM-4.7", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flash", Name: "GLM-4.7-Flash", Reasoning: true},
|
||||
{ID: "zai/glm-4.7-flashx", Name: "GLM-4.7-FlashX", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"minimax": {
|
||||
@@ -111,23 +157,54 @@ var catalog = map[string]Meta{
|
||||
Sort: 45,
|
||||
DefaultBaseURL: "https://api.minimaxi.com/anthropic",
|
||||
EnvKey: "MINIMAX_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 200000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5"},
|
||||
{ID: "minimax/MiniMax-M2.7", Name: "MiniMax M2.7"},
|
||||
{ID: "minimax/MiniMax-M2.7-highspeed", Name: "MiniMax M2.7 highspeed"},
|
||||
{ID: "minimax/MiniMax-M2.5", Name: "MiniMax M2.5", Reasoning: true},
|
||||
{ID: "minimax/MiniMax-M2.5-highspeed", Name: "MiniMax M2.5 highspeed"},
|
||||
},
|
||||
},
|
||||
"xiaomi": {
|
||||
Key: "xiaomi",
|
||||
DisplayName: "Xiaomi",
|
||||
Sort: 46,
|
||||
DefaultBaseURL: "https://api.xiaomimimo.com/anthropic",
|
||||
EnvKey: "XIAOMI_API_KEY",
|
||||
Default: RuntimeDefault{
|
||||
APIType: "anthropic-messages",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "xiaomi/mimo-v2-pro", Name: "Xiaomi MiMo V2 Pro"},
|
||||
{ID: "xiaomi/mimo-v2-omni", Name: "Xiaomi MiMo V2 Omni"},
|
||||
{ID: "xiaomi/mimo-v2-tts", Name: "Xiaomi MiMo V2 TTS"},
|
||||
{ID: "xiaomi/mimo-v2-flash", Name: "Xiaomi MiMo V2 Flash"},
|
||||
},
|
||||
},
|
||||
"kimi": {
|
||||
Key: "kimi",
|
||||
DisplayName: "Kimi (CN)",
|
||||
Sort: 50,
|
||||
DefaultBaseURL: "https://api.moonshot.cn/v1",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "kimi/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
{ID: "kimi/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"kimi-coding": {
|
||||
@@ -136,9 +213,14 @@ var catalog = map[string]Meta{
|
||||
Sort: 51,
|
||||
DefaultBaseURL: "https://api.kimi.com/coding/",
|
||||
EnvKey: "KIMI_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 262144,
|
||||
MaxTokens: 32768,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5"},
|
||||
{ID: "kimi-coding/k2p5", Name: "Kimi K2.5", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"openai": {
|
||||
@@ -147,14 +229,18 @@ var catalog = map[string]Meta{
|
||||
Sort: 55,
|
||||
DefaultBaseURL: "https://api.openai.com/v1",
|
||||
EnvKey: "OPENAI_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openai/codex-mini-latest", Name: "Codex Mini"},
|
||||
{ID: "openai/gpt-4.1", Name: "GPT-4.1"},
|
||||
{ID: "openai/gpt-4o", Name: "GPT-4o"},
|
||||
{ID: "openai/gpt-4o-mini", Name: "GPT-4o Mini"},
|
||||
{ID: "openai/gpt-5", Name: "GPT-5"},
|
||||
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini"},
|
||||
{ID: "openai/codex-mini-latest", Name: "Codex Mini", Reasoning: true},
|
||||
{ID: "openai/gpt-5", Name: "GPT-5", Reasoning: true},
|
||||
{ID: "openai/gpt-5-mini", Name: "GPT-5 Mini", Reasoning: true},
|
||||
{ID: "openai/gpt-5.4", Name: "GPT-5.4", Reasoning: true},
|
||||
{ID: "openai/gpt-5.3-codex", Name: "GPT-5.3-Codex", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"openrouter": {
|
||||
@@ -163,7 +249,12 @@ var catalog = map[string]Meta{
|
||||
Sort: 56,
|
||||
DefaultBaseURL: "https://openrouter.ai/api/v1",
|
||||
EnvKey: "OPENROUTER_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 128000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "openrouter/free", Name: "openrouter/free"},
|
||||
{ID: "openrouter/auto", Name: "openrouter/auto"},
|
||||
@@ -175,13 +266,18 @@ var catalog = map[string]Meta{
|
||||
Sort: 60,
|
||||
DefaultBaseURL: "https://api.anthropic.com",
|
||||
EnvKey: "ANTHROPIC_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"},
|
||||
{ID: "anthropic/claude-3-5-haiku-latest", Name: "Claude 3.5 Haiku"},
|
||||
{ID: "anthropic/claude-3-5-sonnet-20241022", Name: "Claude 3.5 Sonnet"},
|
||||
{ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet"},
|
||||
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1"},
|
||||
{ID: "anthropic/claude-3-7-sonnet-20250219", Name: "Claude 3.7 Sonnet", Reasoning: true},
|
||||
{ID: "anthropic/claude-opus-4-1", Name: "Claude Opus 4.1", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"gemini": {
|
||||
@@ -190,11 +286,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 65,
|
||||
DefaultBaseURL: "https://generativelanguage.googleapis.com",
|
||||
EnvKey: "GEMINI_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text", "image"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview"},
|
||||
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview", Reasoning: true},
|
||||
{ID: "google/gemini-flash-latest", Name: "Gemini Flash Latest"},
|
||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview"},
|
||||
{ID: "google/gemini-3-pro-preview", Name: "Gemini 3 Pro Preview", Reasoning: true},
|
||||
},
|
||||
},
|
||||
"moonshot": {
|
||||
@@ -203,11 +304,16 @@ var catalog = map[string]Meta{
|
||||
Sort: 70,
|
||||
DefaultBaseURL: "https://api.moonshot.ai/v1",
|
||||
EnvKey: "MOONSHOT_API_KEY",
|
||||
Enabled: true,
|
||||
Default: RuntimeDefault{
|
||||
APIType: "openai-completions",
|
||||
ContextWindow: 256000,
|
||||
MaxTokens: 8192,
|
||||
Input: []string{"text"},
|
||||
},
|
||||
Models: []Model{
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5"},
|
||||
{ID: "moonshot/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
|
||||
{ID: "moonshot/kimi-k2-0905-preview", Name: "Kimi K2 0905 Preview"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking"},
|
||||
{ID: "moonshot/kimi-k2-thinking", Name: "Kimi K2 Thinking", Reasoning: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -228,11 +334,6 @@ func All() map[string]Meta {
|
||||
return result
|
||||
}
|
||||
|
||||
func IsEnabled(key string) bool {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
return ok && meta.Enabled
|
||||
}
|
||||
|
||||
func DefaultBaseURL(key string) (string, bool) {
|
||||
meta, ok := catalog[strings.ToLower(strings.TrimSpace(key))]
|
||||
if !ok || strings.TrimSpace(meta.DefaultBaseURL) == "" {
|
||||
@@ -260,11 +361,80 @@ func DisplayName(key string) string {
|
||||
return meta.DisplayName
|
||||
}
|
||||
|
||||
func FindModel(key, modelID string) (Model, bool) {
|
||||
meta, ok := Get(key)
|
||||
if !ok {
|
||||
return Model{}, false
|
||||
}
|
||||
for _, item := range meta.Models {
|
||||
if item.ID == modelID {
|
||||
return item, true
|
||||
}
|
||||
}
|
||||
return Model{}, false
|
||||
}
|
||||
|
||||
func cloneMeta(meta Meta) Meta {
|
||||
clone := meta
|
||||
if len(meta.Default.Input) > 0 {
|
||||
clone.Default.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Default.Input, meta.Default.Input)
|
||||
}
|
||||
if len(meta.Models) > 0 {
|
||||
clone.Models = make([]Model, len(meta.Models))
|
||||
copy(clone.Models, meta.Models)
|
||||
for i, item := range meta.Models {
|
||||
clone.Models[i] = normalizeModel(meta, item)
|
||||
}
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func normalizeModel(meta Meta, model Model) Model {
|
||||
clone := model
|
||||
clone.ID = strings.TrimSpace(clone.ID)
|
||||
clone.Name = strings.TrimSpace(clone.Name)
|
||||
if clone.Name == "" {
|
||||
clone.Name = clone.ID
|
||||
}
|
||||
if clone.MaxTokens <= 0 {
|
||||
clone.MaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if clone.ContextWindow <= 0 {
|
||||
clone.ContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
if len(clone.Input) == 0 && len(meta.Default.Input) > 0 {
|
||||
clone.Input = make([]string, len(meta.Default.Input))
|
||||
copy(clone.Input, meta.Default.Input)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func ResolveRuntimeParams(provider, apiType string, maxTokens, contextWindow int) (string, int, int) {
|
||||
defaultAPIType := "openai-completions"
|
||||
defaultMaxTokens := 8192
|
||||
defaultContextWindow := 256000
|
||||
if meta, ok := Get(provider); ok {
|
||||
if meta.Default.APIType != "" {
|
||||
defaultAPIType = meta.Default.APIType
|
||||
}
|
||||
if meta.Default.MaxTokens > 0 {
|
||||
defaultMaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
if meta.Default.ContextWindow > 0 {
|
||||
defaultContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
}
|
||||
resolvedAPI := apiType
|
||||
if strings.TrimSpace(apiType) == "" {
|
||||
resolvedAPI = defaultAPIType
|
||||
}
|
||||
resolvedMaxTokens := defaultMaxTokens
|
||||
resolvedContextWindow := defaultContextWindow
|
||||
if maxTokens > 0 {
|
||||
resolvedMaxTokens = maxTokens
|
||||
}
|
||||
if contextWindow > 0 {
|
||||
resolvedContextWindow = contextWindow
|
||||
}
|
||||
return resolvedAPI, resolvedMaxTokens, resolvedContextWindow
|
||||
}
|
||||
|
||||
+237
-238
@@ -10,263 +10,322 @@ type OpenClawPatch struct {
|
||||
Models map[string]interface{}
|
||||
}
|
||||
|
||||
func BuildOpenClawPatch(provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) (*OpenClawPatch, error) {
|
||||
type openClawModelSpec struct {
|
||||
ID string
|
||||
Name string
|
||||
Reasoning bool
|
||||
Input []string
|
||||
ContextWindow int
|
||||
MaxTokens int
|
||||
}
|
||||
|
||||
type openClawPatchSpec struct {
|
||||
PrimaryModel string
|
||||
Provider string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
APIType string
|
||||
AuthHeader bool
|
||||
Model openClawModelSpec
|
||||
}
|
||||
|
||||
func BuildOpenClawPatch(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) (*OpenClawPatch, error) {
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
modelName = strings.TrimSpace(modelName)
|
||||
if modelName == "" {
|
||||
return nil, fmt.Errorf("model is required")
|
||||
}
|
||||
apiType, maxTokens, contextWindow = ResolveRuntimeParams(provider, apiType, maxTokens, contextWindow)
|
||||
modelID := modelName
|
||||
if parts := strings.SplitN(modelName, "/", 2); len(parts) == 2 {
|
||||
modelID = parts[1]
|
||||
}
|
||||
|
||||
var spec openClawPatchSpec
|
||||
switch provider {
|
||||
case "deepseek":
|
||||
return buildDeepseekPatch(modelName, baseURL, apiKey), nil
|
||||
spec = buildDeepseekPatchSpec(modelName, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "gemini":
|
||||
return buildGeminiPatch(modelName), nil
|
||||
spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "moonshot", "kimi":
|
||||
return buildMoonshotPatch(provider, modelName, modelID, baseURL, apiKey), nil
|
||||
spec = buildMoonshotPatchSpec(provider, modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "bailian-coding-plan":
|
||||
return buildBailianPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildBailianPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "ark-coding-plan":
|
||||
return buildArkPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildArkPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "minimax":
|
||||
return buildMiniMaxPatch(modelID, baseURL, apiKey), nil
|
||||
spec = buildMiniMaxPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "xiaomi":
|
||||
spec = buildXiaomiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "custom", "vllm":
|
||||
return buildCustomPatch(provider, modelName, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildCustomPatchSpec(provider, modelName, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "ollama":
|
||||
return buildOllamaPatch(modelName, modelID, apiType, baseURL), nil
|
||||
spec = buildOllamaPatchSpec(modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL)
|
||||
case "kimi-coding":
|
||||
return buildKimiCodingPatch(modelName, modelID, baseURL, apiKey), nil
|
||||
spec = buildKimiCodingPatchSpec(modelName, modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
case "zai":
|
||||
return buildZaiPatch(modelID, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildZaiPatchSpec(modelID, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
default:
|
||||
return buildGenericPatch(provider, modelName, modelID, apiType, maxTokens, contextWindow, baseURL, apiKey), nil
|
||||
spec = buildGenericPatchSpec(provider, modelName, modelID, apiType, reasoning, maxTokens, contextWindow, baseURL, apiKey)
|
||||
}
|
||||
return buildOpenClawPatch(spec), nil
|
||||
}
|
||||
|
||||
func buildGeminiPatch(modelName string) *OpenClawPatch {
|
||||
return &OpenClawPatch{PrimaryModel: modelName}
|
||||
}
|
||||
|
||||
func buildDeepseekPatch(modelName, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildOpenClawPatch(spec openClawPatchSpec) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("deepseek", strings.TrimSpace(apiKey), firstNonEmpty(strings.TrimSpace(baseURL), "https://api.deepseek.com/v1"), "openai-completions", map[string]interface{}{
|
||||
"id": "deepseek-chat",
|
||||
"name": "DeepSeek Chat",
|
||||
"reasoning": false,
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 128000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
PrimaryModel: spec.PrimaryModel,
|
||||
Models: providerModels(
|
||||
spec.Provider,
|
||||
spec.APIKey,
|
||||
spec.BaseURL,
|
||||
spec.APIType,
|
||||
spec.AuthHeader,
|
||||
buildOpenClawModel(spec.Model),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func buildMoonshotPatch(provider, modelName, modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildOpenClawModel(spec openClawModelSpec) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": spec.ID,
|
||||
"name": spec.Name,
|
||||
"reasoning": spec.Reasoning,
|
||||
"input": spec.Input,
|
||||
"contextWindow": spec.ContextWindow,
|
||||
"maxTokens": spec.MaxTokens,
|
||||
"cost": map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func buildDeepseekPatchSpec(modelName string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: modelName,
|
||||
Provider: "deepseek",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: "deepseek-chat",
|
||||
Name: "DeepSeek Chat",
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildMoonshotPatchSpec(provider, modelName, modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
configProvider := provider
|
||||
primaryModel := modelName
|
||||
if provider == "kimi" {
|
||||
configProvider = "moonshot"
|
||||
primaryModel = "moonshot/" + modelID
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: primaryModel,
|
||||
Models: providerModels(configProvider, strings.TrimSpace(apiKey), withCatalogDefault(provider, baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": strings.Contains(strings.ToLower(modelID), "thinking"),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 256000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
Provider: configProvider,
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildBailianPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeBailianCodingPlanModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "bailian-coding-plan/" + bailianPrimaryModelID(normalizedID),
|
||||
Models: providerModels("bailian-coding-plan", strings.TrimSpace(apiKey), withCatalogDefault("bailian-coding-plan", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": normalizedID,
|
||||
"reasoning": isReasoningModel(normalizedID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
func buildBailianPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "bailian-coding-plan/" + modelID,
|
||||
Provider: "bailian-coding-plan",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildArkPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeArkCodingPlanModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "ark-coding-plan/" + normalizedID,
|
||||
Models: providerModels("ark-coding-plan", strings.TrimSpace(apiKey), withCatalogDefault("ark-coding-plan", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": normalizedID,
|
||||
"reasoning": isReasoningModel(normalizedID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
func buildArkPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "ark-coding-plan/" + modelID,
|
||||
Provider: "ark-coding-plan",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildMiniMaxPatch(modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
normalizedID := normalizeMiniMaxModelID(modelID)
|
||||
return &OpenClawPatch{
|
||||
PrimaryModel: "minimax-portal/" + normalizedID,
|
||||
Models: providerModels("minimax-portal", strings.TrimSpace(apiKey), firstNonEmpty(strings.TrimSpace(baseURL), "https://api.minimaxi.com/anthropic"), "anthropic-messages", map[string]interface{}{
|
||||
"id": normalizedID,
|
||||
"name": strings.ReplaceAll(normalizedID, "-", " "),
|
||||
"reasoning": false,
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 200000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
func buildMiniMaxPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "minimax/" + modelID,
|
||||
Provider: "minimax",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "anthropic-messages",
|
||||
AuthHeader: true,
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildCustomPatch(provider, modelName, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildXiaomiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
normalizedID := strings.TrimSpace(modelID)
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "xiaomi/" + normalizedID,
|
||||
Provider: "xiaomi",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "anthropic-messages",
|
||||
Model: openClawModelSpec{
|
||||
ID: normalizedID,
|
||||
Name: normalizedID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildCustomPatchSpec(provider, modelName, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
customModelID := normalizeCustomModel(modelName)
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: provider + "/" + customModelID,
|
||||
Models: providerModels(provider, strings.TrimSpace(apiKey), strings.TrimSpace(baseURL), apiType, map[string]interface{}{
|
||||
"id": customModelID,
|
||||
"name": customModelID,
|
||||
"reasoning": isReasoningModel(customModelID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 128000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
Provider: provider,
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: strings.TrimSpace(baseURL),
|
||||
APIType: apiType,
|
||||
Model: openClawModelSpec{
|
||||
ID: customModelID,
|
||||
Name: customModelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildOllamaPatch(modelName, modelID, apiType, baseURL string) *OpenClawPatch {
|
||||
api := normalizeAPIType(apiType)
|
||||
if api != "openai-completions" && api != "openai-responses" {
|
||||
api = "openai-responses"
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
func buildOllamaPatchSpec(modelName, modelID, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("ollama", "ollama", strings.TrimSpace(baseURL), api, map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": api != "openai-completions",
|
||||
"input": []string{"text"},
|
||||
"contextWindow": 160000,
|
||||
"maxTokens": 8192,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
Provider: "ollama",
|
||||
APIKey: "ollama",
|
||||
BaseURL: strings.TrimSpace(baseURL),
|
||||
APIType: apiType,
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildKimiCodingPatch(modelName, modelID, baseURL, apiKey string) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
func buildKimiCodingPatchSpec(modelName, modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: modelName,
|
||||
Models: providerModels("kimi-coding", strings.TrimSpace(apiKey), withCatalogDefault("kimi-coding", baseURL), "anthropic-messages", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": "Kimi for Coding",
|
||||
"reasoning": true,
|
||||
"input": []string{"text", "image"},
|
||||
"contextWindow": 262144,
|
||||
"maxTokens": 32768,
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
Provider: "kimi-coding",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "anthropic-messages",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: "Kimi for Coding",
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text", "image"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildZaiPatch(modelID string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
return &OpenClawPatch{
|
||||
func buildZaiPatchSpec(modelID string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: "zai/" + modelID,
|
||||
Models: providerModels("zai", strings.TrimSpace(apiKey), withCatalogDefault("zai", baseURL), "openai-completions", map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": zaiModelDisplayName(modelID),
|
||||
"reasoning": modelID == "glm-5",
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 204800),
|
||||
"maxTokens": fallbackInt(maxTokens, 131072),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
Provider: "zai",
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: "openai-completions",
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildGenericPatch(provider, modelName, modelID, apiType string, maxTokens, contextWindow int, baseURL, apiKey string) *OpenClawPatch {
|
||||
func buildGenericPatchSpec(provider, modelName, modelID, apiType string, reasoning bool, maxTokens, contextWindow int, baseURL, apiKey string) openClawPatchSpec {
|
||||
providerName := provider
|
||||
primaryModel := modelName
|
||||
if provider == "gemini" {
|
||||
providerName = "google"
|
||||
primaryModel = "google/" + modelID
|
||||
}
|
||||
return &OpenClawPatch{
|
||||
return openClawPatchSpec{
|
||||
PrimaryModel: primaryModel,
|
||||
Models: providerModels(providerName, strings.TrimSpace(apiKey), withCatalogDefault(provider, baseURL), normalizeAPIType(apiType), map[string]interface{}{
|
||||
"id": modelID,
|
||||
"name": modelID,
|
||||
"reasoning": isReasoningModel(modelID),
|
||||
"input": []string{"text"},
|
||||
"contextWindow": fallbackInt(contextWindow, 256000),
|
||||
"maxTokens": fallbackInt(maxTokens, 8192),
|
||||
"cost": map[string]interface{}{},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func providerModels(provider, apiKey, baseURL, api string, model map[string]interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"mode": "merge",
|
||||
"providers": map[string]interface{}{
|
||||
provider: map[string]interface{}{
|
||||
"apiKey": apiKey,
|
||||
"baseUrl": baseURL,
|
||||
"api": api,
|
||||
"models": []map[string]interface{}{model},
|
||||
},
|
||||
Provider: providerName,
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
BaseURL: baseURL,
|
||||
APIType: apiType,
|
||||
Model: openClawModelSpec{
|
||||
ID: modelID,
|
||||
Name: modelID,
|
||||
Reasoning: reasoning,
|
||||
Input: []string{"text"},
|
||||
ContextWindow: contextWindow,
|
||||
MaxTokens: maxTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func withCatalogDefault(provider, baseURL string) string {
|
||||
if strings.TrimSpace(baseURL) != "" {
|
||||
return strings.TrimSpace(baseURL)
|
||||
func providerModels(provider, apiKey, baseURL, api string, authHeader bool, model map[string]interface{}) map[string]interface{} {
|
||||
providerConfig := map[string]interface{}{
|
||||
"apiKey": apiKey,
|
||||
"baseUrl": baseURL,
|
||||
"api": api,
|
||||
"models": []map[string]interface{}{model},
|
||||
}
|
||||
if defaultURL, ok := DefaultBaseURL(provider); ok {
|
||||
return defaultURL
|
||||
if authHeader {
|
||||
providerConfig["authHeader"] = true
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"mode": "merge",
|
||||
"providers": map[string]interface{}{
|
||||
provider: providerConfig,
|
||||
},
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fallbackInt(value, fallback int) int {
|
||||
if value > 0 {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func normalizeAPIType(apiType string) string {
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeCustomModel(modelName string) string {
|
||||
@@ -277,63 +336,3 @@ func normalizeCustomModel(modelName string) string {
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func normalizeBailianCodingPlanModelID(modelID string) string {
|
||||
trim := strings.TrimSpace(modelID)
|
||||
switch strings.ToLower(trim) {
|
||||
case "minimax-m2.5", "minimax m2.5", "minimax/minimax-m2.5", "minimax/minimax m2.5":
|
||||
return "MiniMax/MiniMax-M2.5"
|
||||
default:
|
||||
return trim
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeArkCodingPlanModelID(modelID string) string {
|
||||
return strings.ToLower(strings.TrimSpace(modelID))
|
||||
}
|
||||
|
||||
func normalizeMiniMaxModelID(modelID string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(modelID)) {
|
||||
case "minimax-m2.1", "minimax m2.1", "minimax-m2.1-preview", "minimax-m2.1-latest":
|
||||
return "MiniMax-M2.1"
|
||||
case "minimax-m2.1-lightning", "minimax m2.1 lightning":
|
||||
return "MiniMax-M2.1-lightning"
|
||||
default:
|
||||
return modelID
|
||||
}
|
||||
}
|
||||
|
||||
func zaiModelDisplayName(modelID string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(modelID)) {
|
||||
case "glm-5":
|
||||
return "GLM-5"
|
||||
case "glm-4.7":
|
||||
return "GLM-4.7"
|
||||
case "glm-4.7-flash":
|
||||
return "GLM-4.7-Flash"
|
||||
case "glm-4.7-flashx":
|
||||
return "GLM-4.7-FlashX"
|
||||
default:
|
||||
return strings.TrimSpace(modelID)
|
||||
}
|
||||
}
|
||||
|
||||
func bailianPrimaryModelID(modelID string) string {
|
||||
trim := strings.TrimSpace(modelID)
|
||||
if trim == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(trim, "/")
|
||||
for i := len(parts) - 1; i >= 0; i-- {
|
||||
part := strings.TrimSpace(parts[i])
|
||||
if part != "" {
|
||||
return part
|
||||
}
|
||||
}
|
||||
return trim
|
||||
}
|
||||
|
||||
func isReasoningModel(modelID string) bool {
|
||||
trim := strings.ToLower(strings.TrimSpace(modelID))
|
||||
return strings.Contains(trim, "reason") || strings.Contains(trim, "thinking")
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ type VerifyRequest struct {
|
||||
Body []byte
|
||||
}
|
||||
|
||||
const (
|
||||
defaultVerifyTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func SkipVerification(key string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(key)) {
|
||||
case "custom", "vllm", "ollama", "kimi-coding":
|
||||
@@ -42,7 +46,7 @@ func VerifyAccount(provider, baseURL, apiKey string) error {
|
||||
for key, value := range req.Headers {
|
||||
httpReq.Header.Set(key, value)
|
||||
}
|
||||
resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(httpReq)
|
||||
resp, err := (&http.Client{Timeout: verifyTimeout()}).Do(httpReq)
|
||||
if err != nil {
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||
}
|
||||
@@ -53,6 +57,10 @@ func VerifyAccount(provider, baseURL, apiKey string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyTimeout() time.Duration {
|
||||
return defaultVerifyTimeout
|
||||
}
|
||||
|
||||
func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
base := strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
@@ -115,17 +123,53 @@ func BuildVerifyRequest(provider, baseURL, apiKey string) VerifyRequest {
|
||||
})
|
||||
case "minimax":
|
||||
request.Method = http.MethodPost
|
||||
if !strings.Contains(base, "/v1") {
|
||||
base = base + "/v1"
|
||||
}
|
||||
request.URL = base + "/chat/completions"
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
headers["x-api-key"] = apiKey
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
headers["Content-Type"] = "application/json"
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/messages"
|
||||
} else {
|
||||
request.URL = base + "/v1/messages"
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "MiniMax-M2.1",
|
||||
"messages": []map[string]string{{"role": "user", "content": "test"}},
|
||||
"model": "MiniMax-M2.5",
|
||||
"max_tokens": 1,
|
||||
"messages": []map[string]interface{}{{
|
||||
"role": "user",
|
||||
"content": []map[string]string{{
|
||||
"type": "text",
|
||||
"text": "test",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
case "xiaomi":
|
||||
request.Method = http.MethodPost
|
||||
headers["x-api-key"] = apiKey
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
headers["Content-Type"] = "application/json"
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/messages"
|
||||
} else {
|
||||
request.URL = base + "/v1/messages"
|
||||
}
|
||||
request.Body = mustJSON(map[string]interface{}{
|
||||
"model": "mimo-v2-flash",
|
||||
"max_tokens": 1,
|
||||
"messages": []map[string]interface{}{{
|
||||
"role": "user",
|
||||
"content": []map[string]string{{
|
||||
"type": "text",
|
||||
"text": "test",
|
||||
}},
|
||||
}},
|
||||
})
|
||||
case "openrouter":
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
request.URL = base + "/key"
|
||||
} else {
|
||||
request.URL = base + "/v1/key"
|
||||
}
|
||||
default:
|
||||
headers["Authorization"] = fmt.Sprintf("Bearer %s", apiKey)
|
||||
if strings.Contains(base, "/v1") {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package repo
|
||||
|
||||
import "github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
|
||||
type AgentAccountModelRepo struct{}
|
||||
|
||||
type IAgentAccountModelRepo interface {
|
||||
List(opts ...DBOption) ([]model.AgentAccountModel, error)
|
||||
GetFirst(opts ...DBOption) (*model.AgentAccountModel, error)
|
||||
Create(item *model.AgentAccountModel) error
|
||||
Save(item *model.AgentAccountModel) error
|
||||
DeleteByID(id uint) error
|
||||
Delete(opts ...DBOption) error
|
||||
}
|
||||
|
||||
func NewIAgentAccountModelRepo() IAgentAccountModelRepo {
|
||||
return &AgentAccountModelRepo{}
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) List(opts ...DBOption) ([]model.AgentAccountModel, error) {
|
||||
var list []model.AgentAccountModel
|
||||
if err := getDb(opts...).Find(&list).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) GetFirst(opts ...DBOption) (*model.AgentAccountModel, error) {
|
||||
var item model.AgentAccountModel
|
||||
if err := getDb(opts...).First(&item).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) Create(item *model.AgentAccountModel) error {
|
||||
return getDb().Create(item).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) Save(item *model.AgentAccountModel) error {
|
||||
return getDb().Save(item).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) DeleteByID(id uint) error {
|
||||
return getDb().Delete(&model.AgentAccountModel{}, id).Error
|
||||
}
|
||||
|
||||
func (a AgentAccountModelRepo) Delete(opts ...DBOption) error {
|
||||
return getDb(opts...).Delete(&model.AgentAccountModel{}).Error
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump {
|
||||
}
|
||||
if !withAll && len(quicks) == 0 {
|
||||
return []model.QuickJump{
|
||||
{Name: "Agent", Title: "aiTools.agents.agents", Recommend: 1, IsShow: true, Router: "/ai/agents/agent"},
|
||||
{Name: "Agent", Title: "aiTools.agents.agent", Recommend: 1, IsShow: true, Router: "/ai/agents/agent"},
|
||||
{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"},
|
||||
{Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"},
|
||||
{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: false, Router: "/cronjobs"},
|
||||
|
||||
@@ -19,6 +19,12 @@ func WithByID(id uint) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithByGroupID(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("group_id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func WithByNOTID(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("id != ?", id)
|
||||
@@ -43,6 +49,12 @@ func WithByName(name string) DBOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithByAddr(addr string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("addr = ?", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func WithByKey(key string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("key = ?", key)
|
||||
|
||||
@@ -10,6 +10,18 @@ import (
|
||||
type HostRepo struct{}
|
||||
|
||||
type IHostRepo interface {
|
||||
Get(opts ...DBOption) (model.Host, error)
|
||||
GetList(opts ...DBOption) ([]model.Host, error)
|
||||
Page(limit, offset int, opts ...DBOption) (int64, []model.Host, error)
|
||||
Create(host *model.Host) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
UpdateGroup(group, newGroup uint) error
|
||||
Delete(opts ...DBOption) error
|
||||
|
||||
WithByInfo(info string) DBOption
|
||||
WithByPort(port uint) DBOption
|
||||
WithByUser(user string) DBOption
|
||||
|
||||
GetFirewallRecord(opts ...DBOption) (model.Firewall, error)
|
||||
ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error)
|
||||
SaveFirewallRecord(firewall *model.Firewall) error
|
||||
@@ -30,6 +42,80 @@ func NewIHostRepo() IHostRepo {
|
||||
return &HostRepo{}
|
||||
}
|
||||
|
||||
func (h *HostRepo) Get(opts ...DBOption) (model.Host, error) {
|
||||
var host model.Host
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&host).Error
|
||||
return host, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetList(opts ...DBOption) ([]model.Host, error) {
|
||||
var hosts []model.Host
|
||||
db := global.DB.Model(&model.Host{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&hosts).Error
|
||||
return hosts, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) Page(page, size int, opts ...DBOption) (int64, []model.Host, error) {
|
||||
var hosts []model.Host
|
||||
db := global.DB.Model(&model.Host{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&hosts).Error
|
||||
return count, hosts, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) WithByInfo(info string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(info) == 0 {
|
||||
return g
|
||||
}
|
||||
infoStr := "%" + info + "%"
|
||||
return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HostRepo) WithByPort(port uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("port = ?", port)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HostRepo) WithByUser(user string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("user = ?", user)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HostRepo) Create(host *model.Host) error {
|
||||
return global.DB.Create(host).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) UpdateGroup(group, newGroup uint) error {
|
||||
return global.DB.Model(&model.Host{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) Delete(opts ...DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Host{}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) {
|
||||
var firewall model.Firewall
|
||||
db := global.DB
|
||||
|
||||
+349
-1949
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,619 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
func (a AgentService) GetFeishuConfig(req dto.AgentFeishuConfigReq) (*dto.AgentFeishuConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractFeishuConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateFeishuConfig(req dto.AgentFeishuConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setFeishuConfig(conf, dto.AgentFeishuConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotName: req.BotName,
|
||||
AppID: req.AppID,
|
||||
AppSecret: req.AppSecret,
|
||||
})
|
||||
setFeishuPluginEnabled(conf, req.Enabled)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetTelegramConfig(req dto.AgentTelegramConfigReq) (*dto.AgentTelegramConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractTelegramConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateTelegramConfig(req dto.AgentTelegramConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setTelegramConfig(conf, dto.AgentTelegramConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotToken: req.BotToken,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDiscordConfig(req dto.AgentIDReq) (*dto.AgentDiscordConfig, error) {
|
||||
_, _, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDiscordConfig(conf)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDiscordConfig(req dto.AgentDiscordConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDiscordConfig(conf, dto.AgentDiscordConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
Token: req.Token,
|
||||
Proxy: req.Proxy,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetQQBotConfig(req dto.AgentIDReq) (*dto.AgentQQBotConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractQQBotConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "qqbot")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setQQBotConfig(conf, dto.AgentQQBotConfig{
|
||||
Enabled: req.Enabled,
|
||||
AppID: req.AppID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetWecomConfig(req dto.AgentIDReq) (*dto.AgentWecomConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractWecomConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "wecom")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setWecomConfig(conf, dto.AgentWecomConfig{
|
||||
Enabled: req.Enabled,
|
||||
DmPolicy: req.DmPolicy,
|
||||
BotID: req.BotID,
|
||||
Secret: req.Secret,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) GetDingTalkConfig(req dto.AgentIDReq) (*dto.AgentDingTalkConfig, error) {
|
||||
_, install, conf, err := a.loadAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := extractDingTalkConfig(conf)
|
||||
installed, _ := checkPluginInstalled(install.ContainerName, "dingtalk")
|
||||
result.Installed = installed
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error {
|
||||
return a.mutateAgentConfig(req.AgentID, func(_ *model.Agent, _ *model.AppInstall, conf map[string]interface{}) error {
|
||||
setDingTalkConfig(conf, dto.AgentDingTalkConfig{
|
||||
Enabled: req.Enabled,
|
||||
ClientID: req.ClientID,
|
||||
ClientSecret: req.ClientSecret,
|
||||
DmPolicy: req.DmPolicy,
|
||||
AllowFrom: req.AllowFrom,
|
||||
GroupPolicy: req.GroupPolicy,
|
||||
GroupAllowFrom: req.GroupAllowFrom,
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
|
||||
agent, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec, pluginID, err := resolvePluginMeta(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(req.Type, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask("Install OpenClaw plugin", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
if req.Type == "qqbot" {
|
||||
legacyPluginPath := path.Join(openclawPluginBaseDir, "qqbot")
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", install.ContainerName, legacyPluginPath); err == nil {
|
||||
if err := mgr.Run("docker", "exec", "-i", install.ContainerName, "sh", "-c", "printf 'yes\\n' | openclaw plugins uninstall qqbot"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appendPluginAllow(conf, pluginID)
|
||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("install openclaw plugin failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
loginTask, err := task.NewTaskWithOps("weixin", task.TaskExec, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
loginTask.AddSubTask("Login OpenClaw Weixin channel", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(30*time.Minute))
|
||||
return mgr.RunBashCf("docker exec %s openclaw channels login --channel openclaw-weixin", install.ContainerName)
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := loginTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("login openclaw weixin channel failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installed, err := checkPluginInstalled(install.ContainerName, req.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AgentPluginStatus{Installed: installed}, nil
|
||||
}
|
||||
|
||||
func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveReq) error {
|
||||
_, install, err := a.loadAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cmd.RunDefaultBashCf(
|
||||
"docker exec %s openclaw pairing approve %s %q",
|
||||
install.ContainerName,
|
||||
req.Type,
|
||||
strings.TrimSpace(req.PairingCode),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFeishuConfig(conf map[string]interface{}) dto.AgentFeishuConfig {
|
||||
result := dto.AgentFeishuConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
feishu, ok := channels["feishu"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := feishu["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := feishu["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
accounts, ok := feishu["accounts"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
main, ok := accounts["main"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if appID, ok := main["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if appSecret, ok := main["appSecret"].(string); ok {
|
||||
result.AppSecret = appSecret
|
||||
}
|
||||
if botName, ok := main["botName"].(string); ok {
|
||||
result.BotName = botName
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setFeishuConfig(conf map[string]interface{}, config dto.AgentFeishuConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
feishu := ensureChildMap(channels, "feishu")
|
||||
feishu["enabled"] = config.Enabled
|
||||
feishu["dmPolicy"] = config.DmPolicy
|
||||
|
||||
accounts := ensureChildMap(feishu, "accounts")
|
||||
main := ensureChildMap(accounts, "main")
|
||||
main["appId"] = config.AppID
|
||||
main["appSecret"] = config.AppSecret
|
||||
main["botName"] = config.BotName
|
||||
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
feishu["allowFrom"] = []string{"*"}
|
||||
}
|
||||
}
|
||||
|
||||
func setFeishuPluginEnabled(conf map[string]interface{}, enabled bool) {
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
feishu := ensureChildMap(entries, "feishu")
|
||||
feishu["enabled"] = enabled
|
||||
}
|
||||
|
||||
func extractTelegramConfig(conf map[string]interface{}) dto.AgentTelegramConfig {
|
||||
result := dto.AgentTelegramConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
telegram, ok := channels["telegram"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := telegram["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := telegram["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if botToken, ok := telegram["botToken"].(string); ok {
|
||||
result.BotToken = botToken
|
||||
}
|
||||
if proxy, ok := telegram["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setTelegramConfig(conf map[string]interface{}, config dto.AgentTelegramConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
telegram := map[string]interface{}{
|
||||
"enabled": config.Enabled,
|
||||
"dmPolicy": config.DmPolicy,
|
||||
"botToken": config.BotToken,
|
||||
}
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
telegram["allowFrom"] = []string{"*"}
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
telegram["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
}
|
||||
channels["telegram"] = telegram
|
||||
}
|
||||
|
||||
func extractDiscordConfig(conf map[string]interface{}) dto.AgentDiscordConfig {
|
||||
result := dto.AgentDiscordConfig{Enabled: true, DmPolicy: "pairing", GroupPolicy: "open"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
discord, ok := channels["discord"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := discord["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if token, ok := discord["token"].(string); ok {
|
||||
result.Token = token
|
||||
}
|
||||
if groupPolicy, ok := discord["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
if proxy, ok := discord["proxy"].(string); ok {
|
||||
result.Proxy = proxy
|
||||
}
|
||||
if policy, ok := discord["dmPolicy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
return result
|
||||
}
|
||||
dm, ok := discord["dm"].(map[string]interface{})
|
||||
if ok {
|
||||
if policy, ok := dm["policy"].(string); ok && strings.TrimSpace(policy) != "" {
|
||||
result.DmPolicy = policy
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func setDiscordConfig(conf map[string]interface{}, config dto.AgentDiscordConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
discord := ensureChildMap(channels, "discord")
|
||||
discord["enabled"] = config.Enabled
|
||||
discord["token"] = config.Token
|
||||
discord["dmPolicy"] = config.DmPolicy
|
||||
discord["groupPolicy"] = config.GroupPolicy
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
discord["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
delete(discord, "allowFrom")
|
||||
}
|
||||
if strings.TrimSpace(config.Proxy) != "" {
|
||||
discord["proxy"] = strings.TrimSpace(config.Proxy)
|
||||
} else {
|
||||
delete(discord, "proxy")
|
||||
}
|
||||
delete(discord, "dm")
|
||||
}
|
||||
|
||||
func extractQQBotConfig(conf map[string]interface{}) dto.AgentQQBotConfig {
|
||||
result := dto.AgentQQBotConfig{Enabled: true}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
qqbot, ok := channels["qqbot"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := qqbot["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if appID, ok := qqbot["appId"].(string); ok {
|
||||
result.AppID = appID
|
||||
}
|
||||
if clientSecret, ok := qqbot["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractWecomConfig(conf map[string]interface{}) dto.AgentWecomConfig {
|
||||
result := dto.AgentWecomConfig{Enabled: true, DmPolicy: "pairing"}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
wecom, ok := channels["wecom"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := wecom["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if dmPolicy, ok := wecom["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = strings.TrimSpace(dmPolicy)
|
||||
}
|
||||
if botID, ok := wecom["botId"].(string); ok {
|
||||
result.BotID = botID
|
||||
}
|
||||
if secret, ok := wecom["secret"].(string); ok {
|
||||
result.Secret = secret
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func extractDingTalkConfig(conf map[string]interface{}) dto.AgentDingTalkConfig {
|
||||
result := dto.AgentDingTalkConfig{
|
||||
Enabled: true,
|
||||
DmPolicy: "pairing",
|
||||
GroupPolicy: "disabled",
|
||||
AllowFrom: []string{},
|
||||
GroupAllowFrom: []string{},
|
||||
}
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
dingtalk, ok := channels["dingtalk-connector"].(map[string]interface{})
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
if enabled, ok := dingtalk["enabled"].(bool); ok {
|
||||
result.Enabled = enabled
|
||||
}
|
||||
if clientID, ok := dingtalk["clientId"].(string); ok {
|
||||
result.ClientID = clientID
|
||||
}
|
||||
if clientSecret, ok := dingtalk["clientSecret"].(string); ok {
|
||||
result.ClientSecret = clientSecret
|
||||
}
|
||||
if dmPolicy, ok := dingtalk["dmPolicy"].(string); ok && strings.TrimSpace(dmPolicy) != "" {
|
||||
result.DmPolicy = dmPolicy
|
||||
}
|
||||
if groupPolicy, ok := dingtalk["groupPolicy"].(string); ok && strings.TrimSpace(groupPolicy) != "" {
|
||||
result.GroupPolicy = groupPolicy
|
||||
}
|
||||
result.AllowFrom = extractStringList(dingtalk["allowFrom"])
|
||||
result.GroupAllowFrom = extractStringList(dingtalk["groupAllowFrom"])
|
||||
return result
|
||||
}
|
||||
|
||||
func setWecomConfig(conf map[string]interface{}, config dto.AgentWecomConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
wecom := ensureChildMap(channels, "wecom")
|
||||
wecom["enabled"] = config.Enabled
|
||||
wecom["botId"] = strings.TrimSpace(config.BotID)
|
||||
wecom["secret"] = strings.TrimSpace(config.Secret)
|
||||
wecom["dmPolicy"] = strings.TrimSpace(config.DmPolicy)
|
||||
if strings.EqualFold(config.DmPolicy, "open") {
|
||||
wecom["allowFrom"] = []string{"*"}
|
||||
} else {
|
||||
wecom["allowFrom"] = []string{}
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
wecomEntry := ensureChildMap(entries, "wecom-openclaw-plugin")
|
||||
wecomEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func setDingTalkConfig(conf map[string]interface{}, config dto.AgentDingTalkConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
dingtalk := ensureChildMap(channels, "dingtalk-connector")
|
||||
dingtalk["enabled"] = config.Enabled
|
||||
dingtalk["clientId"] = strings.TrimSpace(config.ClientID)
|
||||
dingtalk["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
dingtalk["dmPolicy"] = config.DmPolicy
|
||||
dingtalk["groupPolicy"] = config.GroupPolicy
|
||||
dingtalk["gatewayToken"] = extractGatewayToken(conf)
|
||||
switch config.DmPolicy {
|
||||
case "open":
|
||||
dingtalk["allowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["allowFrom"] = append([]string(nil), config.AllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "allowFrom")
|
||||
}
|
||||
switch config.GroupPolicy {
|
||||
case "open":
|
||||
dingtalk["groupAllowFrom"] = []string{"*"}
|
||||
case "allowlist":
|
||||
dingtalk["groupAllowFrom"] = append([]string(nil), config.GroupAllowFrom...)
|
||||
default:
|
||||
delete(dingtalk, "groupAllowFrom")
|
||||
}
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
dingtalkEntry := ensureChildMap(entries, "dingtalk-connector")
|
||||
dingtalkEntry["enabled"] = config.Enabled
|
||||
|
||||
gateway := ensureChildMap(conf, "gateway")
|
||||
httpMap := ensureChildMap(gateway, "http")
|
||||
endpoints := ensureChildMap(httpMap, "endpoints")
|
||||
chatCompletions := ensureChildMap(endpoints, "chatCompletions")
|
||||
chatCompletions["enabled"] = true
|
||||
}
|
||||
|
||||
func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
|
||||
channels := ensureChildMap(conf, "channels")
|
||||
qqbot := ensureChildMap(channels, "qqbot")
|
||||
delete(qqbot, "dmPolicy")
|
||||
qqbot["enabled"] = config.Enabled
|
||||
qqbot["allowFrom"] = []string{"*"}
|
||||
qqbot["appId"] = strings.TrimSpace(config.AppID)
|
||||
qqbot["clientSecret"] = strings.TrimSpace(config.ClientSecret)
|
||||
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
entries := ensureChildMap(plugins, "entries")
|
||||
delete(entries, "qqbot")
|
||||
qqbotEntry := ensureChildMap(entries, "openclaw-qqbot")
|
||||
qqbotEntry["enabled"] = config.Enabled
|
||||
}
|
||||
|
||||
func appendPluginAllow(conf map[string]interface{}, pluginID string) {
|
||||
plugins := ensureChildMap(conf, "plugins")
|
||||
allow := make([]string, 0, 4)
|
||||
seen := map[string]struct{}{}
|
||||
switch values := plugins["allow"].(type) {
|
||||
case []interface{}:
|
||||
for _, value := range values {
|
||||
text, ok := value.(string)
|
||||
if !ok || text == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[text]; ok {
|
||||
continue
|
||||
}
|
||||
seen[text] = struct{}{}
|
||||
allow = append(allow, text)
|
||||
}
|
||||
}
|
||||
if _, ok := seen[pluginID]; ok {
|
||||
plugins["allow"] = allow
|
||||
return
|
||||
}
|
||||
plugins["allow"] = append(allow, pluginID)
|
||||
}
|
||||
|
||||
func buildOpenclawPluginInstallScript(spec, pluginID string) string {
|
||||
return fmt.Sprintf(
|
||||
"set -e; workdir=%s/%s; rm -rf \"$workdir\"; mkdir -p \"$workdir\"; cd \"$workdir\"; npm pack --silent %q >/dev/null 2>&1; pkg=$(find \"$workdir\" -maxdepth 1 -type f -name '*.tgz' | head -n 1); printf '%%s\\n' \"$pkg\"; openclaw plugins install \"$pkg\"; rm -rf \"$workdir\"",
|
||||
openclawPluginPackageTmpDir,
|
||||
pluginID,
|
||||
spec,
|
||||
)
|
||||
}
|
||||
|
||||
func resolvePluginMeta(pluginType string) (string, string, error) {
|
||||
switch pluginType {
|
||||
case "qqbot":
|
||||
return "@tencent-connect/openclaw-qqbot@latest", "openclaw-qqbot", nil
|
||||
case "wecom":
|
||||
return "@wecom/wecom-openclaw-plugin", "wecom-openclaw-plugin", nil
|
||||
case "dingtalk":
|
||||
return "@dingtalk-real-ai/dingtalk-connector", "dingtalk-connector", nil
|
||||
case "weixin":
|
||||
return "@tencent-weixin/openclaw-weixin", "openclaw-weixin", nil
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported plugin type")
|
||||
}
|
||||
}
|
||||
|
||||
func checkPluginInstalled(containerName, pluginType string) (bool, error) {
|
||||
_, pluginDir, err := resolvePluginMeta(pluginType)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if strings.TrimSpace(containerName) == "" {
|
||||
return false, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
pluginPath := path.Join(openclawPluginBaseDir, pluginDir)
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second))
|
||||
if err := mgr.RunBashCf("docker exec %s test -d %s", containerName, pluginPath); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
openclawCronJobsRelativePath = "data/conf/cron/jobs.json"
|
||||
openclawSessionStoreRelativeGlob = "data/conf/agents/*/sessions/sessions.json"
|
||||
openclawSessionFilesRelativeGlob = "data/conf/agents/*/sessions/*.jsonl"
|
||||
)
|
||||
|
||||
func (a AgentService) GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error) {
|
||||
agent, install, conf, err := a.loadOpenclawAgentConfig(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
overview := &dto.AgentOverview{
|
||||
Snapshot: dto.AgentOverviewSnapshot{
|
||||
ContainerStatus: install.Status,
|
||||
AppVersion: install.Version,
|
||||
DefaultModel: extractOpenclawDefaultModel(conf),
|
||||
ChannelCount: countOpenclawConfiguredChannels(conf),
|
||||
},
|
||||
}
|
||||
if overview.Snapshot.DefaultModel == "" {
|
||||
overview.Snapshot.DefaultModel = agent.Model
|
||||
}
|
||||
if install.Status != constant.StatusRunning {
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
skillCount, err := loadOpenclawOverviewSkillStats(install.ContainerName)
|
||||
if err == nil {
|
||||
overview.Snapshot.SkillCount = skillCount
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
sessionCount, err := loadOpenclawOverviewSessionCount(install.GetPath())
|
||||
if err == nil {
|
||||
overview.Snapshot.SessionCount = sessionCount
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
jobCount, err := loadOpenclawOverviewJobCount(install.GetPath())
|
||||
if err == nil {
|
||||
overview.Snapshot.JobCount = jobCount
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
func extractOpenclawDefaultModel(conf map[string]interface{}) string {
|
||||
agents, ok := conf["agents"].(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
defaults, ok := agents["defaults"].(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
model, ok := defaults["model"].(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
primary, _ := model["primary"].(string)
|
||||
return strings.TrimSpace(primary)
|
||||
}
|
||||
|
||||
func countOpenclawConfiguredChannels(conf map[string]interface{}) int {
|
||||
channels, ok := conf["channels"].(map[string]interface{})
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
count := 0
|
||||
for _, value := range channels {
|
||||
channel, ok := value.(map[string]interface{})
|
||||
if !ok || len(channel) == 0 {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func loadOpenclawOverviewSkillStats(containerName string) (int, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills list --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if strings.TrimSpace(output) == "" {
|
||||
return 0, nil
|
||||
}
|
||||
skills, err := parseOpenclawSkillsList(output)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(skills), nil
|
||||
}
|
||||
|
||||
func loadOpenclawOverviewSessionCount(installPath string) (int, error) {
|
||||
sessionStorePaths, err := filepath.Glob(filepath.Join(installPath, openclawSessionStoreRelativeGlob))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total := 0
|
||||
for _, sessionStorePath := range sessionStorePaths {
|
||||
count, err := loadOpenclawSessionCountFromFile(sessionStorePath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
total += count
|
||||
}
|
||||
if total > 0 {
|
||||
return total, nil
|
||||
}
|
||||
sessionFiles, err := filepath.Glob(filepath.Join(installPath, openclawSessionFilesRelativeGlob))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(sessionFiles), nil
|
||||
}
|
||||
|
||||
func loadOpenclawSessionCountFromFile(sessionStorePath string) (int, error) {
|
||||
content, err := os.ReadFile(sessionStorePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseOpenclawSessionCount(string(content))
|
||||
}
|
||||
|
||||
func loadOpenclawOverviewJobCount(installPath string) (int, error) {
|
||||
content, err := os.ReadFile(filepath.Join(installPath, openclawCronJobsRelativePath))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return parseOpenclawCronCount(string(content))
|
||||
}
|
||||
|
||||
func parseOpenclawSessionCount(output string) (int, error) {
|
||||
if strings.TrimSpace(output) == "" {
|
||||
return 0, nil
|
||||
}
|
||||
var payload interface{}
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return countOpenclawSessions(payload), nil
|
||||
}
|
||||
|
||||
func parseOpenclawCronCount(output string) (int, error) {
|
||||
if strings.TrimSpace(output) == "" {
|
||||
return 0, nil
|
||||
}
|
||||
var payload interface{}
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch value := payload.(type) {
|
||||
case []interface{}:
|
||||
return len(value), nil
|
||||
case map[string]interface{}:
|
||||
if total, ok := value["total"].(float64); ok {
|
||||
return int(total), nil
|
||||
}
|
||||
if jobs, ok := value["jobs"].([]interface{}); ok {
|
||||
return len(jobs), nil
|
||||
}
|
||||
return len(value), nil
|
||||
default:
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
func countOpenclawSessions(payload interface{}) int {
|
||||
switch value := payload.(type) {
|
||||
case []interface{}:
|
||||
return len(value)
|
||||
case map[string]interface{}:
|
||||
if count, ok := value["count"].(float64); ok {
|
||||
return int(count)
|
||||
}
|
||||
if sessions, ok := value["sessions"]; ok {
|
||||
return countOpenclawSessions(sessions)
|
||||
}
|
||||
count := 0
|
||||
for _, item := range value {
|
||||
switch typed := item.(type) {
|
||||
case map[string]interface{}:
|
||||
if looksLikeOpenclawSession(typed) {
|
||||
count++
|
||||
}
|
||||
case string:
|
||||
if strings.HasSuffix(strings.TrimSpace(typed), ".jsonl") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func looksLikeOpenclawSession(item map[string]interface{}) bool {
|
||||
if _, ok := item["sessionFile"]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := item["agentId"]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := item["lastActivityTs"]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := item["updatedAt"]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/task"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
type openclawSkillsList struct {
|
||||
Skills []openclawSkillListItem `json:"skills"`
|
||||
}
|
||||
|
||||
type openclawSkillListItem struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Source string `json:"source"`
|
||||
Bundled bool `json:"bundled"`
|
||||
Disabled bool `json:"disabled"`
|
||||
}
|
||||
|
||||
type openclawSkillInfo struct {
|
||||
SkillKey string `json:"skillKey"`
|
||||
}
|
||||
|
||||
type skillhubSearchPayload struct {
|
||||
Skills []dto.AgentSkillSearchItem `json:"skills"`
|
||||
Results []dto.AgentSkillSearchItem `json:"results"`
|
||||
}
|
||||
|
||||
var clawhubSearchLinePattern = regexp.MustCompile(`^(\S+)\s+(.+?)\s+\(([\d.]+)\)$`)
|
||||
|
||||
func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, error) {
|
||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills list --json 2>&1",
|
||||
30*time.Second,
|
||||
install.ContainerName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseOpenclawSkillsList(output)
|
||||
}
|
||||
|
||||
func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error) {
|
||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output, err := loadOpenclawSkillSearchOutput(install.ContainerName, req.Source, req.Keyword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
switch req.Source {
|
||||
case "skillhub":
|
||||
return parseSkillhubSearchResult(output)
|
||||
default:
|
||||
return parseClawhubSearchResult(output), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error {
|
||||
agent, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
conf, err := readOpenclawConfig(agent.ConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
skillKey, err := getOpenclawSkillKey(install.ContainerName, req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setOpenclawSkillEnabled(conf, skillKey, req.Enabled)
|
||||
return writeOpenclawConfigRaw(agent.ConfigPath, conf)
|
||||
}
|
||||
|
||||
func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error {
|
||||
_, install, err := a.loadOpenclawAgentAndInstall(req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureContainerRunning(install.ContainerName); err != nil {
|
||||
return err
|
||||
}
|
||||
installTask, err := task.NewTaskWithOps(req.Slug, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installTask.AddSubTask("Install OpenClaw skill", func(t *task.Task) error {
|
||||
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
|
||||
return mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawSkillInstallCommand(req.Source, req.Slug))
|
||||
}, nil)
|
||||
go func() {
|
||||
if err := installTask.Execute(); err != nil {
|
||||
global.LOG.Errorf("install openclaw skill failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) {
|
||||
var payload openclawSkillsList
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]dto.AgentSkillItem, 0, len(payload.Skills))
|
||||
for _, item := range payload.Skills {
|
||||
items = append(items, dto.AgentSkillItem{
|
||||
Name: item.Name,
|
||||
Description: item.Description,
|
||||
Source: item.Source,
|
||||
Bundled: item.Bundled,
|
||||
Disabled: item.Disabled,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (string, error) {
|
||||
switch source {
|
||||
case "skillhub":
|
||||
return cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s skillhub search %q --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
keyword,
|
||||
)
|
||||
default:
|
||||
return cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s clawhub search %q 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSkillhubSearchResult(output string) ([]dto.AgentSkillSearchItem, error) {
|
||||
trimmed := strings.TrimSpace(output)
|
||||
if trimmed == "" {
|
||||
return nil, nil
|
||||
}
|
||||
var list []dto.AgentSkillSearchItem
|
||||
if err := json.Unmarshal([]byte(trimmed), &list); err == nil {
|
||||
for i := range list {
|
||||
list[i].Source = "skillhub"
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
var payload skillhubSearchPayload
|
||||
if err := json.Unmarshal([]byte(trimmed), &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := payload.Skills
|
||||
if len(items) == 0 {
|
||||
items = payload.Results
|
||||
}
|
||||
for i := range items {
|
||||
items[i].Source = "skillhub"
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
items := make([]dto.AgentSkillSearchItem, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
matches := clawhubSearchLinePattern.FindStringSubmatch(strings.TrimSpace(line))
|
||||
if len(matches) != 4 {
|
||||
continue
|
||||
}
|
||||
items = append(items, dto.AgentSkillSearchItem{
|
||||
Slug: matches[1],
|
||||
Name: matches[2],
|
||||
Score: matches[3],
|
||||
Source: "clawhub",
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func buildOpenclawSkillInstallCommand(source, slug string) string {
|
||||
switch source {
|
||||
case "clawhub":
|
||||
return fmt.Sprintf(
|
||||
"mkdir -p %s && clawhub --workdir /home/node/.openclaw --dir skills install %q",
|
||||
openclawManagedSkillsDir,
|
||||
slug,
|
||||
)
|
||||
default:
|
||||
return fmt.Sprintf(
|
||||
"mkdir -p %s && skillhub --dir %s install %q",
|
||||
openclawManagedSkillsDir,
|
||||
openclawManagedSkillsDir,
|
||||
slug,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getOpenclawSkillKey(containerName, name string) (string, error) {
|
||||
output, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut(
|
||||
"docker exec %s openclaw skills info %q --json 2>&1",
|
||||
30*time.Second,
|
||||
containerName,
|
||||
name,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return parseOpenclawSkillKey(name, output)
|
||||
}
|
||||
|
||||
func parseOpenclawSkillKey(name, output string) (string, error) {
|
||||
var payload openclawSkillInfo
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if payload.SkillKey == "" {
|
||||
return "", fmt.Errorf("skill %s does not have a skillKey", name)
|
||||
}
|
||||
return payload.SkillKey, nil
|
||||
}
|
||||
|
||||
func setOpenclawSkillEnabled(conf map[string]interface{}, skillKey string, enabled bool) {
|
||||
skills := ensureChildMap(conf, "skills")
|
||||
entries := ensureChildMap(skills, "entries")
|
||||
entry := ensureChildMap(entries, skillKey)
|
||||
entry["enabled"] = enabled
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,6 +57,10 @@ type IAppService interface {
|
||||
GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error)
|
||||
}
|
||||
|
||||
type appInstallHooks struct {
|
||||
AfterCopyData func(appInstall *model.AppInstall) error
|
||||
}
|
||||
|
||||
func NewIAppService() IAppService {
|
||||
return &AppService{}
|
||||
}
|
||||
@@ -340,6 +344,10 @@ func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) {
|
||||
}
|
||||
|
||||
func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (appInstall *model.AppInstall, err error) {
|
||||
return a.installWithHooks(req, executeScript, nil)
|
||||
}
|
||||
|
||||
func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript bool, hooks *appInstallHooks) (appInstall *model.AppInstall, err error) {
|
||||
if err = docker.CreateDefaultDockerNetwork(); err != nil {
|
||||
err = buserr.WithDetail("Err1PanelNetworkFailed", err.Error(), nil)
|
||||
return
|
||||
@@ -537,6 +545,11 @@ func (a AppService) Install(req request.AppInstallCreate, executeScript bool) (a
|
||||
if err = copyData(t, app, appDetail, appInstall, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if hooks != nil && hooks.AfterCopyData != nil {
|
||||
if err = hooks.AfterCopyData(appInstall); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if executeScript {
|
||||
if err = runScript(t, appInstall, "init"); err != nil {
|
||||
return err
|
||||
|
||||
@@ -337,12 +337,11 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changePort := false
|
||||
oldInstalled := installed
|
||||
port, ok := req.Params["PANEL_APP_PORT_HTTP"]
|
||||
if ok {
|
||||
portN := int(math.Ceil(port.(float64)))
|
||||
if portN != installed.HttpPort {
|
||||
changePort = true
|
||||
httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -428,9 +427,17 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
installed.Status = constant.StatusRunning
|
||||
_ = appInstallRepo.Save(context.Background(), &installed)
|
||||
|
||||
proxyChanged := hasAppInstallProxyPassChanged(&oldInstalled, &installed)
|
||||
currentProxy, currentProxyErr := getAppInstallProxyPass(&installed)
|
||||
website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID))
|
||||
if changePort && website.ID != 0 && website.Status == constant.StatusRunning {
|
||||
go func() {
|
||||
if proxyChanged && website.ID != 0 && currentProxyErr == nil {
|
||||
website.Proxy = currentProxy
|
||||
if err := websiteRepo.SaveWithoutCtx(&website); err != nil {
|
||||
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error())
|
||||
}
|
||||
}
|
||||
if proxyChanged && website.ID != 0 && website.Status == constant.StatusRunning && currentProxyErr == nil {
|
||||
go func(website model.Website, proxy string) {
|
||||
nginxInstall, err := getNginxFull(&website)
|
||||
if err != nil {
|
||||
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error())
|
||||
@@ -443,7 +450,6 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
return
|
||||
}
|
||||
server := servers[0]
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
|
||||
if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil {
|
||||
@@ -454,7 +460,10 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
}(website, currentProxy)
|
||||
}
|
||||
if proxyChanged && website.ID != 0 && currentProxyErr != nil {
|
||||
global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", currentProxyErr).Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -802,7 +802,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error {
|
||||
}
|
||||
|
||||
var newCompose string
|
||||
if err = migrateOpenclawHTTPSUpgrade(&install, oldVersion, detail.Version); err != nil {
|
||||
if err = migrateOpenclawProtocolUpgrade(&install, oldVersion, detail.Version); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.DockerCompose == "" {
|
||||
|
||||
@@ -1189,7 +1189,7 @@ func (u *ContainerService) ListContainerFiles(req dto.ContainerFileReq) ([]dto.C
|
||||
ctx := context.Background()
|
||||
stat, err := cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, normalizeContainerFileError(err)
|
||||
}
|
||||
isDir := stat.Mode.IsDir()
|
||||
isLink := stat.Mode&os.ModeSymlink != 0
|
||||
@@ -1205,7 +1205,7 @@ func (u *ContainerService) ListContainerFiles(req dto.ContainerFileReq) ([]dto.C
|
||||
|
||||
output, err := runContainerCommand(cli, req.ContainerID, []string{"ls", "-1A", "--", req.Path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, normalizeContainerFileError(err)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
files := make([]dto.ContainerFileInfo, 0, len(lines))
|
||||
@@ -1348,7 +1348,7 @@ func (u *ContainerService) GetContainerFileContent(req dto.ContainerFileReq) (*d
|
||||
|
||||
stat, err := cli.ContainerStatPath(context.Background(), req.ContainerID, req.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, normalizeContainerFileError(err)
|
||||
}
|
||||
if stat.Mode.IsDir() {
|
||||
return nil, fmt.Errorf("path %s is directory", req.Path)
|
||||
@@ -1395,7 +1395,7 @@ func (u *ContainerService) GetContainerFileSize(req dto.ContainerFileReq) (int64
|
||||
|
||||
stat, err := cli.ContainerStatPath(context.Background(), req.ContainerID, req.Path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, normalizeContainerFileError(err)
|
||||
}
|
||||
if !stat.Mode.IsDir() {
|
||||
return stat.Size, nil
|
||||
@@ -1428,7 +1428,7 @@ func (u *ContainerService) DownloadContainerFile(req dto.ContainerFileReq) (io.R
|
||||
stat, err := cli.ContainerStatPath(ctx, req.ContainerID, req.Path)
|
||||
if err != nil {
|
||||
_ = cli.Close()
|
||||
return nil, "", "", err
|
||||
return nil, "", "", normalizeContainerFileError(err)
|
||||
}
|
||||
|
||||
fileName := stat.Name
|
||||
@@ -1474,6 +1474,17 @@ func (u *ContainerService) DownloadContainerFile(req dto.ContainerFileReq) (io.R
|
||||
}, fileName, "application/octet-stream", nil
|
||||
}
|
||||
|
||||
func normalizeContainerFileError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
message := strings.ToLower(err.Error())
|
||||
if strings.Contains(message, "no such file or directory") || strings.Contains(message, "not found") {
|
||||
return buserr.New("ErrPathNotFound")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func runContainerCommand(cli *client.Client, containerID string, command []string) (string, error) {
|
||||
raw, err := runContainerCommandRaw(cli, containerID, command)
|
||||
if err != nil {
|
||||
|
||||
@@ -135,6 +135,7 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
|
||||
for key, value := range mergedMap {
|
||||
value.Name = key
|
||||
value.ComposeFileExists = composeFileExists(value.Workdir, value.ConfigFile)
|
||||
records = append(records, value)
|
||||
}
|
||||
if len(req.Info) != 0 {
|
||||
@@ -164,6 +165,29 @@ func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface
|
||||
return int64(total), listItem, nil
|
||||
}
|
||||
|
||||
func composeFileExists(workdir, configFile string) bool {
|
||||
workdir = strings.TrimSpace(workdir)
|
||||
configFile = strings.TrimSpace(configFile)
|
||||
if configFile == "" {
|
||||
return false
|
||||
}
|
||||
for _, item := range strings.Split(configFile, ",") {
|
||||
file := strings.TrimSpace(item)
|
||||
if file == "" {
|
||||
continue
|
||||
}
|
||||
if !filepath.IsAbs(file) && workdir != "" {
|
||||
file = filepath.Join(workdir, file)
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
info, err := os.Stat(file)
|
||||
if err == nil && !info.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) {
|
||||
if cmd.CheckIllegal(req.Path) {
|
||||
return false, buserr.New("ErrCmdIllegal")
|
||||
@@ -383,8 +407,11 @@ func (u *ContainerService) loadPath(req *dto.ComposeCreate) error {
|
||||
}
|
||||
|
||||
func removeContainerForCompose(composeName, composePath string) error {
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
if _, err := os.Stat(composePath); err == nil {
|
||||
if stdout, err := compose.Operate(composePath, "down"); err != nil {
|
||||
return errors.New(stdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var options container.ListOptions
|
||||
options.All = true
|
||||
|
||||
@@ -12,11 +12,12 @@ var (
|
||||
appInstallResourceRepo = repo.NewIAppInstallResourceRpo()
|
||||
appIgnoreUpgradeRepo = repo.NewIAppIgnoreUpgradeRepo()
|
||||
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
|
||||
agentRepo = repo.NewIAgentRepo()
|
||||
agentAccountRepo = repo.NewIAgentAccountRepo()
|
||||
aiRepo = repo.NewIAiRepo()
|
||||
mcpServerRepo = repo.NewIMcpServerRepo()
|
||||
tensorrtLLMRepo = repo.NewITensorRTLLMRepo()
|
||||
agentRepo = repo.NewIAgentRepo()
|
||||
agentAccountRepo = repo.NewIAgentAccountRepo()
|
||||
agentAccountModelRepo = repo.NewIAgentAccountModelRepo()
|
||||
|
||||
mysqlRepo = repo.NewIMysqlRepo()
|
||||
postgresqlRepo = repo.NewIPostgresqlRepo()
|
||||
|
||||
@@ -4,13 +4,12 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/core/buserr"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -75,11 +74,7 @@ func (u *HostService) TestByInfo(req dto.HostConnTest) bool {
|
||||
}
|
||||
|
||||
func (u *HostService) TestLocalConn(id uint) bool {
|
||||
var (
|
||||
host model.Host
|
||||
err error
|
||||
)
|
||||
host, err = hostRepo.Get(repo.WithByID(id))
|
||||
host, err := hostRepo.Get(repo.WithByID(id))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -113,12 +108,11 @@ func (u *HostService) TestLocalConn(id uint) bool {
|
||||
return false
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interface{}, error) {
|
||||
var options []global.DBOption
|
||||
var options []repo.DBOption
|
||||
if len(req.Info) != 0 {
|
||||
options = append(options, hostRepo.WithByInfo(req.Info))
|
||||
}
|
||||
@@ -198,8 +192,7 @@ func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, e
|
||||
|
||||
func (u *HostService) GetHostByID(id uint) (*dto.HostInfo, error) {
|
||||
var item dto.HostInfo
|
||||
var host model.Host
|
||||
host, _ = hostRepo.Get(repo.WithByID(id))
|
||||
host, _ := hostRepo.Get(repo.WithByID(id))
|
||||
if host.ID == 0 {
|
||||
return nil, buserr.New("ErrRecordNotFound")
|
||||
}
|
||||
@@ -336,7 +329,6 @@ func GetHostInfo(id uint) (*model.Host, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(host.PassPhrase) != 0 {
|
||||
host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase)
|
||||
if err != nil {
|
||||
@@ -3,6 +3,8 @@ package service
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
@@ -12,6 +14,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/constant"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ssh"
|
||||
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
@@ -19,7 +22,9 @@ type SettingService struct{}
|
||||
|
||||
type ISettingService interface {
|
||||
GetSettingInfo() (*dto.SettingInfo, error)
|
||||
GetTerminalAIInfo() (*dto.TerminalAIInfo, error)
|
||||
Update(key, value string) error
|
||||
UpdateTerminalAI(req dto.TerminalAIInfo) error
|
||||
|
||||
TestConnByInfo(req dto.SSHConnData) bool
|
||||
SaveConnInfo(req dto.SSHConnData) error
|
||||
@@ -57,10 +62,67 @@ func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) {
|
||||
return &info, err
|
||||
}
|
||||
|
||||
func (u *SettingService) GetTerminalAIInfo() (*dto.TerminalAIInfo, error) {
|
||||
info := &dto.TerminalAIInfo{
|
||||
AIStatus: constant.StatusDisable,
|
||||
AIAccountID: "",
|
||||
AIPrefix: constant.DefaultTerminalAIPrefix,
|
||||
AIRiskCommands: "[]",
|
||||
AIRiskCommandsDefault: constant.DefaultTerminalAIRiskCommands,
|
||||
}
|
||||
|
||||
if value, err := settingRepo.GetValueByKey("AIStatus"); err == nil && value != "" {
|
||||
info.AIStatus = value
|
||||
}
|
||||
if value, err := settingRepo.GetValueByKey("AIAccountID"); err == nil {
|
||||
info.AIAccountID = value
|
||||
}
|
||||
if value, err := settingRepo.GetValueByKey("AIPrefix"); err == nil {
|
||||
info.AIPrefix = value
|
||||
}
|
||||
if value, err := settingRepo.GetValueByKey("AIRiskCommands"); err == nil && value != "" {
|
||||
info.AIRiskCommands = value
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (u *SettingService) Update(key, value string) error {
|
||||
return settingRepo.UpdateOrCreate(key, value)
|
||||
}
|
||||
|
||||
func (u *SettingService) UpdateTerminalAI(req dto.TerminalAIInfo) error {
|
||||
if strings.EqualFold(strings.TrimSpace(req.AIStatus), constant.StatusEnable) {
|
||||
accountID, err := strconv.ParseUint(strings.TrimSpace(req.AIAccountID), 10, 64)
|
||||
if err != nil || accountID == 0 {
|
||||
return buserr.New("ErrAgentAccountIDRequired")
|
||||
}
|
||||
currentStatus, _ := settingRepo.GetValueByKey("AIStatus")
|
||||
currentAccountID, _ := settingRepo.GetValueByKey("AIAccountID")
|
||||
needValidate := !strings.EqualFold(strings.TrimSpace(currentStatus), constant.StatusEnable) ||
|
||||
strings.TrimSpace(currentAccountID) != strings.TrimSpace(req.AIAccountID)
|
||||
if needValidate {
|
||||
if err := terminalai.ValidateTerminalAccount(uint(accountID)); err != nil {
|
||||
return buserr.WithErr("ErrAgentAccountUnavailable", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate("AIStatus", req.AIStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate("AIAccountID", req.AIAccountID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate("AIPrefix", req.AIPrefix); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := settingRepo.UpdateOrCreate("AIRiskCommands", req.AIRiskCommands); err != nil {
|
||||
return err
|
||||
}
|
||||
terminalai.InvalidateTerminalRuntimeCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *SettingService) TestConnByInfo(req dto.SSHConnData) bool {
|
||||
if req.AuthMode == "password" && len(req.Password) != 0 {
|
||||
password, err := base64.StdEncoding.DecodeString(req.Password)
|
||||
|
||||
@@ -389,7 +389,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
}
|
||||
appInstall = install
|
||||
website.AppInstallID = install.ID
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
website.Proxy, err = getAppInstallProxyPass(appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var install model.AppInstall
|
||||
install, err = appInstallRepo.GetFirst(repo.WithByID(create.AppInstallID))
|
||||
@@ -399,7 +402,10 @@ func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error)
|
||||
configApp := func(t *task.Task) error {
|
||||
appInstall = &install
|
||||
website.AppInstallID = appInstall.ID
|
||||
website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort)
|
||||
website.Proxy, err = getAppInstallProxyPass(appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
createTask.AddSubTask(i18n.GetMsgByKey("ConfigApp"), configApp, nil)
|
||||
@@ -1488,23 +1494,11 @@ func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err e
|
||||
newBlock.AppendDirectives(ifDir)
|
||||
}
|
||||
if website.Type == constant.Deployment {
|
||||
newBlock.AppendDirectives(
|
||||
&components.Directive{
|
||||
Name: "proxy_set_header",
|
||||
Parameters: []string{"Host", "$host"},
|
||||
},
|
||||
&components.Directive{
|
||||
Name: "proxy_set_header",
|
||||
Parameters: []string{"X-Real-IP", "$remote_addr"},
|
||||
},
|
||||
&components.Directive{
|
||||
Name: "proxy_set_header",
|
||||
Parameters: []string{"X-Forwarded-For", "$proxy_add_x_forwarded_for"},
|
||||
},
|
||||
&components.Directive{
|
||||
Name: "proxy_pass",
|
||||
Parameters: []string{fmt.Sprintf("http://%s", website.Proxy)},
|
||||
})
|
||||
proxyDirectives := getRootProxyDirectives(website.Proxy)
|
||||
if len(proxyDirectives) == 0 {
|
||||
return errors.New("failed to build deployment proxy directives")
|
||||
}
|
||||
newBlock.AppendDirectives(proxyDirectives...)
|
||||
}
|
||||
newDirective.Block = newBlock
|
||||
index := -1
|
||||
@@ -1750,7 +1744,7 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
||||
return buserr.WithErr("ErrUpdateBuWebsite", err)
|
||||
}
|
||||
|
||||
nginxInclude := fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias)
|
||||
nginxInclude := getWebsiteRedirectInclude(website)
|
||||
if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -2328,6 +2322,7 @@ func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error {
|
||||
} else {
|
||||
command = req.ExtCommand
|
||||
}
|
||||
command = strings.TrimSpace(command)
|
||||
resourceName := fmt.Sprintf("composer %s", command)
|
||||
composerTask, err := task.NewTaskWithOps(resourceName, task.TaskExec, req.Command, req.TaskID, website.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -115,6 +115,13 @@ func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error)
|
||||
return
|
||||
}
|
||||
applyLocationProxyPass(location, req.ProxyPass, &req.SNI, req.ProxySSLName)
|
||||
if isHTTPSProxyPass(req.ProxyPass) && req.SSLVerify {
|
||||
location.UpdateDirective("proxy_ssl_verify", []string{"on"})
|
||||
location.UpdateDirective("proxy_ssl_trusted_certificate", []string{"/etc/ssl/certs/ca-certificates.crt"})
|
||||
} else {
|
||||
location.RemoveDirective("proxy_ssl_verify", []string{})
|
||||
location.RemoveDirective("proxy_ssl_trusted_certificate", []string{})
|
||||
}
|
||||
location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost})
|
||||
location.ChangePath(req.Modifier, req.Match)
|
||||
// Server Cache Settings
|
||||
@@ -329,6 +336,9 @@ func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, e
|
||||
if directive.GetName() == "proxy_ssl_name" && len(directive.GetParameters()) > 0 {
|
||||
proxyConfig.ProxySSLName = directive.GetParameters()[0]
|
||||
}
|
||||
if directive.GetName() == "proxy_ssl_verify" {
|
||||
proxyConfig.SSLVerify = len(directive.GetParameters()) > 0 && directive.GetParameters()[0] == "on"
|
||||
}
|
||||
}
|
||||
proxyConfig.Cors = location.Cors
|
||||
proxyConfig.AllowCredentials = location.AllowCredentials
|
||||
|
||||
@@ -51,6 +51,58 @@ func isHTTPSProxyPass(proxyPass string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(proxyPass)), "https://")
|
||||
}
|
||||
|
||||
func normalizeProxyPass(proxyPass string) string {
|
||||
proxyPass = strings.TrimSpace(proxyPass)
|
||||
if proxyPass == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(proxyPass, "://") || strings.HasPrefix(proxyPass, "unix:") {
|
||||
return proxyPass
|
||||
}
|
||||
return fmt.Sprintf("http://%s", proxyPass)
|
||||
}
|
||||
|
||||
func getAppInstallProxyPass(appInstall *model.AppInstall) (string, error) {
|
||||
if appInstall == nil {
|
||||
return "", errors.New("app install is nil")
|
||||
}
|
||||
if appInstall.HttpPort > 0 {
|
||||
return fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort), nil
|
||||
}
|
||||
if appInstall.HttpsPort > 0 {
|
||||
return fmt.Sprintf("https://127.0.0.1:%d", appInstall.HttpsPort), nil
|
||||
}
|
||||
return "", fmt.Errorf("app %s has no available http or https port", appInstall.Name)
|
||||
}
|
||||
|
||||
func getAppInstallProxyPassOrEmpty(appInstall *model.AppInstall) string {
|
||||
proxyPass, err := getAppInstallProxyPass(appInstall)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return proxyPass
|
||||
}
|
||||
|
||||
func hasAppInstallProxyPassChanged(before *model.AppInstall, after *model.AppInstall) bool {
|
||||
return getAppInstallProxyPassOrEmpty(before) != getAppInstallProxyPassOrEmpty(after)
|
||||
}
|
||||
|
||||
func getRootProxyDirectives(proxyPass string) []components.IDirective {
|
||||
server := &components.Server{}
|
||||
server.UpdateRootProxy([]string{normalizeProxyPass(proxyPass)})
|
||||
|
||||
locations := server.FindDirectives("location")
|
||||
if len(locations) == 0 || locations[0].GetBlock() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]components.IDirective(nil), locations[0].GetBlock().GetDirectives()...)
|
||||
}
|
||||
|
||||
func getWebsiteRedirectInclude(website model.Website) string {
|
||||
return fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias)
|
||||
}
|
||||
|
||||
func applyLocationProxyPass(location *components.Location, proxyPass string, sni *bool, proxySSLName string) {
|
||||
location.UpdateDirective("proxy_pass", []string{proxyPass})
|
||||
|
||||
@@ -281,7 +333,10 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
rootIndex := path.Join("/www/sites", website.Alias, "index")
|
||||
switch website.Type {
|
||||
case constant.Deployment:
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
|
||||
proxy, err := getAppInstallProxyPass(appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
case constant.Static:
|
||||
server.UpdateRoot(rootIndex)
|
||||
@@ -303,7 +358,7 @@ func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, a
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, "")
|
||||
}
|
||||
case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet:
|
||||
server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)})
|
||||
server.UpdateRootProxy([]string{normalizeProxyPass(website.Proxy)})
|
||||
}
|
||||
case constant.Subsite:
|
||||
parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID))
|
||||
@@ -1001,7 +1056,7 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
server.RemoveDirective("include", []string{proxyInclude})
|
||||
rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias)
|
||||
server.RemoveDirective("include", []string{rewriteInclude})
|
||||
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
|
||||
redirectInclude := getWebsiteRedirectInclude(*website)
|
||||
server.RemoveDirective("include", []string{redirectInclude})
|
||||
|
||||
switch website.Type {
|
||||
@@ -1033,7 +1088,7 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
if fileOp.Stat(absoluteRewritePath) {
|
||||
server.UpdateDirective("include", []string{rewriteInclude})
|
||||
}
|
||||
redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias)
|
||||
redirectInclude := getWebsiteRedirectInclude(*website)
|
||||
absoluteRedirectPath := GetSitePath(*website, SiteRedirectDir)
|
||||
if fileOp.Stat(absoluteRedirectPath) {
|
||||
server.UpdateDirective("include", []string{redirectInclude})
|
||||
@@ -1049,7 +1104,10 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort)
|
||||
proxy, err := getAppInstallProxyPass(&appInstall)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
case constant.Static:
|
||||
server.UpdateRoot(rootIndex)
|
||||
@@ -1069,7 +1127,7 @@ func opWebsite(website *model.Website, operate string) error {
|
||||
}
|
||||
server.UpdatePHPProxy([]string{website.Proxy}, localPath)
|
||||
} else {
|
||||
proxy := fmt.Sprintf("http://%s", website.Proxy)
|
||||
proxy := normalizeProxyPass(website.Proxy)
|
||||
server.UpdateRootProxy([]string{proxy})
|
||||
}
|
||||
case constant.Subsite:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package constant
|
||||
|
||||
const DefaultTerminalAIPrefix = "@ai"
|
||||
|
||||
const DefaultTerminalAIRiskCommands = "[\"rm\",\"mkfs\",\"dd if=\",\"dd of=/dev/\",\"wipefs\",\"fdisk\",\"parted\",\"sfdisk\",\"shred\",\"curl | sh\",\"wget | sh\",\"chmod -R 777 /\",\"shutdown\",\"reboot\",\"poweroff\",\"init 0\",\":(){ :|:& };:\"]"
|
||||
+46
-38
@@ -1,14 +1,17 @@
|
||||
module github.com/1Panel-dev/1Panel/agent
|
||||
|
||||
go 1.24.9
|
||||
go 1.25.7
|
||||
|
||||
replace github.com/moby/go-archive => github.com/moby/go-archive v0.1.0
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go v1.55.0
|
||||
github.com/compose-spec/compose-go/v2 v2.9.0
|
||||
github.com/compose-spec/compose-go/v2 v2.9.1
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/docker/compose/v2 v2.40.2
|
||||
github.com/docker/docker v28.5.1+incompatible
|
||||
github.com/docker/cli v29.2.0+incompatible
|
||||
github.com/docker/compose/v2 v2.40.3
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
@@ -18,15 +21,15 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/go-resty/resty/v2 v2.17.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/go-sql-driver/mysql v1.9.3
|
||||
github.com/goh-chunlin/go-onedrive v1.1.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/jackc/pgx/v5 v5.6.0
|
||||
github.com/jackc/pgx/v5 v5.7.5
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/klauspost/compress v1.18.1
|
||||
github.com/klauspost/compress v1.18.2
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/miekg/dns v1.1.69
|
||||
github.com/minio/minio-go/v7 v7.0.74
|
||||
@@ -36,12 +39,12 @@ require (
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/qiniu/go-sdk/v7 v7.21.1
|
||||
github.com/qiniu/go-sdk/v7 v7.26.4
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.11
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/subosito/gotenv v1.6.0
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.54
|
||||
@@ -102,7 +105,7 @@ require (
|
||||
github.com/connesc/cipherio v0.2.1 // indirect
|
||||
github.com/containerd/console v1.0.5 // indirect
|
||||
github.com/containerd/containerd/api v1.10.0 // indirect
|
||||
github.com/containerd/containerd/v2 v2.2.0 // indirect
|
||||
github.com/containerd/containerd/v2 v2.2.1 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
@@ -112,17 +115,17 @@ require (
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/buildx v0.29.1 // indirect
|
||||
github.com/docker/cli v28.5.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/buildx v0.31.1 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.5 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-acme/alidns-20150109/v4 v4.7.0 // indirect
|
||||
@@ -145,7 +148,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/h2non/filetype v1.1.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
@@ -155,8 +158,8 @@ require (
|
||||
github.com/in-toto/in-toto-golang v0.9.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
@@ -167,18 +170,19 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/buildkit v0.25.1 // indirect
|
||||
github.com/moby/buildkit v0.27.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.1.0 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/moby/api v1.52.0 // indirect
|
||||
github.com/moby/moby/client v0.2.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
@@ -188,7 +192,7 @@ require (
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/morikuni/aec v1.1.0 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/namedotcom/go/v4 v4.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
@@ -206,13 +210,15 @@ require (
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1 // indirect
|
||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||
github.com/sigstore/sigstore v1.10.0 // indirect
|
||||
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
@@ -222,7 +228,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect
|
||||
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||
@@ -232,34 +238,36 @@ require (
|
||||
github.com/volcengine/volc-sdk-golang v1.0.230 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.39.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
modernc.org/fileutil v1.3.3 // indirect
|
||||
modernc.org/libc v1.66.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
||||
+100
-19
@@ -38,7 +38,6 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -117,6 +116,7 @@ github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7
|
||||
github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
||||
github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
@@ -215,16 +215,21 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo
|
||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.0 h1:UHSv/QHlo6QJtrT4igF1rdORgIUhDo1gWuyJUoiNNIM=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.0/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.1 h1:8UwI+ujNU+9Ffkf/YgAm/qM9/eU7Jn8nHzWG721W4rs=
|
||||
github.com/compose-spec/compose-go/v2 v2.9.1/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
|
||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||
github.com/containerd/cgroups/v3 v3.1.0 h1:azxYVj+91ZgSnIBp2eI3k9y2iYQSR/ZQIgh9vKO+HSY=
|
||||
github.com/containerd/cgroups/v3 v3.1.0/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4=
|
||||
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
|
||||
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o=
|
||||
github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM=
|
||||
github.com/containerd/containerd/v2 v2.2.0 h1:K7TqcXy+LnFmZaui2DgHsnp2gAHhVNWYaHlx7HXfys8=
|
||||
github.com/containerd/containerd/v2 v2.2.0/go.mod h1:YCMjKjA4ZA7egdHNi3/93bJR1+2oniYlnS+c0N62HdE=
|
||||
github.com/containerd/containerd/v2 v2.2.1 h1:TpyxcY4AL5A+07dxETevunVS5zxqzuq7ZqJXknM11yk=
|
||||
github.com/containerd/containerd/v2 v2.2.1/go.mod h1:NR70yW1iDxe84F2iFWbR9xfAN0N2F0NcjTi1OVth4nU=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
@@ -237,13 +242,17 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg=
|
||||
github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM=
|
||||
github.com/containerd/nydus-snapshotter v0.15.4 h1:l59kGRVMtwMLDLh322HsWhEsBCkRKMkGWYV5vBeLYCE=
|
||||
github.com/containerd/nydus-snapshotter v0.15.10 h1:hphjuKOqSHLGznNJiAvmsOWkdu4qFXjf4DzGrWSuIsM=
|
||||
github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4=
|
||||
github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=
|
||||
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y=
|
||||
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8=
|
||||
github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4=
|
||||
github.com/containerd/stargz-snapshotter v0.17.0 h1:djNS4KU8ztFhLdEDZ1bsfzOiYuVHT6TgSU5qwRk+cNc=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.17.0 h1:+TyQIsR/zSFI1Rm31EQBwpAA1ovYgIKHy7kctL3sLcE=
|
||||
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
|
||||
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
@@ -255,7 +264,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -270,16 +278,28 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/buildx v0.29.1 h1:58hxM5Z4mnNje3G5NKfULT9xCr8ooM8XFtlfUK9bKaA=
|
||||
github.com/docker/buildx v0.29.1/go.mod h1:J4EFv6oxlPiV1MjO0VyJx2u5tLM7ImDEl9zyB8d4wPI=
|
||||
github.com/docker/buildx v0.30.1 h1:3vthfaTQOLt5QfN2nl7rKuPLUvx69nL5ZikFIXp//c8=
|
||||
github.com/docker/buildx v0.30.1/go.mod h1:8nwT0V6UNYNo9rXq6WO/BQd9KrJ0JYcY/QX6x0y1Oro=
|
||||
github.com/docker/buildx v0.31.1 h1:zbvbrb9nxBNVV8nnI33f2F+4aAZBA1gY+AmeBFflMqY=
|
||||
github.com/docker/buildx v0.31.1/go.mod h1:SD+jYLnt3S4SXqohVtV+8z+dihnOgwMJ8t+bLQvsaCk=
|
||||
github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY=
|
||||
github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
|
||||
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/compose/v2 v2.40.2 h1:h2bDBJkOuqmj93XvT2oI0ArPQonE0lGtWiILXdiXvbA=
|
||||
github.com/docker/compose/v2 v2.40.2/go.mod h1:CbSJpKGw20LInVsPjglZ8z7Squ3OBQOD7Ux5nkjGfIU=
|
||||
github.com/docker/compose/v2 v2.40.3 h1:XeYkQu1svDtyfZPv5nTwFryQ25ZJMkIlc4pz9HalMPI=
|
||||
github.com/docker/compose/v2 v2.40.3/go.mod h1:iNY1tvoHTyN3C3QHCuWAgj3OjR2T6mGkk/qxfbBF/4M=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
@@ -316,6 +336,8 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
@@ -333,6 +355,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
|
||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg=
|
||||
github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
@@ -379,15 +403,12 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
@@ -397,6 +418,8 @@ github.com/go-resty/resty/v2 v2.17.0/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2m
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
@@ -407,7 +430,6 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
|
||||
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
|
||||
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -489,6 +511,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
@@ -515,6 +538,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4=
|
||||
github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
@@ -556,7 +581,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 h1:uia+R3K1izQRGpxTV+bS4q3/ueMW7ProAMWqM6OlqOU=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI=
|
||||
github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU=
|
||||
github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo=
|
||||
@@ -567,10 +591,16 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
@@ -615,6 +645,8 @@ github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
@@ -631,7 +663,6 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -639,7 +670,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
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/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
@@ -648,14 +678,14 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f h1:B0OD7nYl2FPQEVrw8g2uyc1lGEzNbvrKh7fspGZcbvY=
|
||||
github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f/go.mod h1:aEt7p9Rvh67BYApmZwNDPpgircTO2kgdmDUoF/1QmwA=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
@@ -702,12 +732,22 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/buildkit v0.25.1 h1:j7IlVkeNbEo+ZLoxdudYCHpmTsbwKvhgc/6UJ/mY/o8=
|
||||
github.com/moby/buildkit v0.25.1/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc=
|
||||
github.com/moby/buildkit v0.26.3 h1:D+ruZVAk/3ipRq5XRxBH9/DIFpRjSlTtMbghT5gQP9g=
|
||||
github.com/moby/buildkit v0.26.3/go.mod h1:4T4wJzQS4kYWIfFRjsbJry4QoxDBjK+UGOEOs1izL7w=
|
||||
github.com/moby/buildkit v0.27.0 h1:1gtNaMcVE0XXCZrybC32L79A7Ga1JeB7V3PfpCt1bDc=
|
||||
github.com/moby/buildkit v0.27.0/go.mod h1:4STUkNc5t1nf03HS+01UmI2X6FdfOI3XaKt9QNoTsms=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
|
||||
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
@@ -735,6 +775,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
|
||||
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
|
||||
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -787,8 +829,10 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
|
||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
@@ -808,7 +852,6 @@ github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -853,22 +896,21 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
|
||||
github.com/qiniu/go-sdk/v7 v7.21.1 h1:D/IjVOlg5pTw0jeDjqTo6H5QM73Obb1AYfPOHmIFN+Q=
|
||||
github.com/qiniu/go-sdk/v7 v7.21.1/go.mod h1:8EM2awITynlem2VML2dXGHkMYP2UyECsGLOdp6yMpco=
|
||||
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.4 h1:D/k6cPbhjKvSx9hVBQSh97GuPu1ZtM3OLPCclSpusjo=
|
||||
github.com/qiniu/go-sdk/v7 v7.26.4/go.mod h1:ri7fGwbio0pRDFr8EK5TUpx0DbnpIMJ2bMSDxGWfCbk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
@@ -886,18 +928,26 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCw
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g=
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU=
|
||||
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
|
||||
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
|
||||
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sigstore/sigstore v1.10.0 h1:lQrmdzqlR8p9SCfWIpFoGUqdXEzJSZT2X+lTXOMPaQI=
|
||||
github.com/sigstore/sigstore v1.10.0/go.mod h1:Ygq+L/y9Bm3YnjpJTlQrOk/gXyrjkpn3/AEJpmk1n9Y=
|
||||
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 h1:94NLPmq4bxvdmslzcG670IOkrlS98CGpmob8cjpFHuI=
|
||||
github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7/go.mod h1:4r/PNX0G7uzkLpc3PSdYs5E2k4bWEJNXTK6kwAyw9TM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
@@ -907,12 +957,15 @@ github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIK
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk=
|
||||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
|
||||
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -964,6 +1017,8 @@ github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK5
|
||||
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA=
|
||||
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
|
||||
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
|
||||
@@ -983,6 +1038,7 @@ github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNW
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.230 h1:84/MOF0zUPtAHt3e1+MsFq5qrnQRC+e3XzTUwIOzZxw=
|
||||
github.com/volcengine/volc-sdk-golang v1.0.230/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
@@ -1008,6 +1064,8 @@ go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsX
|
||||
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss=
|
||||
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -1020,20 +1078,33 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
@@ -1045,6 +1116,8 @@ go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTq
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
@@ -1058,6 +1131,7 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU=
|
||||
@@ -1082,7 +1156,6 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -1113,6 +1186,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -1143,6 +1218,8 @@ golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1477,6 +1554,8 @@ google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOs
|
||||
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@@ -1514,6 +1593,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+23
-3
@@ -27,13 +27,18 @@ var langFiles = map[string]string{
|
||||
}
|
||||
|
||||
func GetMsgWithMap(key string, maps map[string]interface{}) string {
|
||||
return GetMsgWithMapAndLang("", key, maps)
|
||||
}
|
||||
|
||||
func GetMsgWithMapAndLang(lang string, key string, maps map[string]interface{}) string {
|
||||
var content string
|
||||
localizer := newLocalizer(lang)
|
||||
if maps == nil {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
content, _ = localizer.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
} else {
|
||||
content, _ = global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
content, _ = localizer.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: maps,
|
||||
})
|
||||
@@ -78,7 +83,11 @@ func GetErrMsg(key string, maps map[string]interface{}) string {
|
||||
}
|
||||
|
||||
func GetMsgByKey(key string) string {
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
return GetMsgByKeyAndLang("", key)
|
||||
}
|
||||
|
||||
func GetMsgByKeyAndLang(lang string, key string) string {
|
||||
content, _ := newLocalizer(lang).Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
})
|
||||
return content
|
||||
@@ -151,6 +160,17 @@ func Init() {
|
||||
global.I18n = i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
|
||||
func newLocalizer(lang string) *i18n.Localizer {
|
||||
lang = strings.TrimSpace(lang)
|
||||
if lang == "" {
|
||||
if global.I18n != nil {
|
||||
return global.I18n
|
||||
}
|
||||
lang = GetLanguageFromDB()
|
||||
}
|
||||
return i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
|
||||
func GetLanguageFromDB() string {
|
||||
if global.DB == nil {
|
||||
return "en"
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: 'Bad request: {{ .detail }}'
|
||||
ErrTokenParse: 'Token error: {{ .detail }}'
|
||||
ErrInitialPassword: 'Original password is incorrect'
|
||||
ErrInternalServer: 'Internal server error: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'Remote download failed or URL is unreachable: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'Remote URL did not return a file (possibly an error page), {{ .detail }}'
|
||||
ErrRecordExist: 'Record already exists'
|
||||
ErrRecordNotFound: 'Record not found'
|
||||
ErrStructTransform: 'Type conversion failed: {{ .err }}'
|
||||
@@ -44,7 +46,10 @@ ErrAgentAccountUnavailable: 'Account connection unavailable: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Unsupported agent provider'
|
||||
ErrAgentAccountRequired: 'Select an agent account first'
|
||||
ErrAgentAccountNotVerified: 'Agent account not verified'
|
||||
ErrAgentProviderMismatch: 'Agent provider mismatch'
|
||||
ErrAgentAccountModelsRequired: 'Please configure at least one model'
|
||||
ErrAgentAccountSingleInitialModel: 'Only one initial model can be configured during account creation'
|
||||
ErrAgentModelNotInAccount: 'Model is not configured in the selected account'
|
||||
ErrAgentModelInUse: 'A bound agent is still using this model: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Base URL is required'
|
||||
ErrAgentApiKeyRequired: 'API key is required'
|
||||
ErrAgentComposeRequired: 'Compose content is required'
|
||||
@@ -562,3 +567,9 @@ PartitionDiskErr: 'Partition failed: {{ .err }}'
|
||||
FormatDiskErr: 'Format failed: {{ .err }}'
|
||||
MountDiskErr: 'Mount failed: {{ .err }}'
|
||||
XfsNotFound: 'xfs not found; install xfsprogs first'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'blocked risky command: {{ .command }}'
|
||||
TerminalAIThinking: 'AI is thinking...'
|
||||
TerminalAIReadyToExecute: 'Thinking complete, press Enter to execute (duration: {{ .duration }}, tokens: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'AI request failed: {{ .err }}'
|
||||
|
||||
@@ -2,6 +2,8 @@ ErrInvalidParams: 'Solicitud inválida: {{ .detail }}'
|
||||
ErrTokenParse: 'Token inválido: {{ .detail }}'
|
||||
ErrInitialPassword: 'Contraseña original incorrecta'
|
||||
ErrInternalServer: 'Error interno: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'La descarga remota falló o la URL no es accesible: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'La URL remota no devolvió un archivo (posiblemente una página de error), {{ .detail }}'
|
||||
ErrRecordExist: 'Registro ya existe'
|
||||
ErrRecordNotFound: 'Registro no encontrado'
|
||||
ErrStructTransform: 'Error de conversión: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'Conexión de cuenta no disponible: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Proveedor de agente no soportado'
|
||||
ErrAgentAccountRequired: 'Elige una cuenta de agente primero'
|
||||
ErrAgentAccountNotVerified: 'Cuenta de agente no verificada'
|
||||
ErrAgentProviderMismatch: 'Proveedor de agente no coincide'
|
||||
ErrAgentAccountModelsRequired: 'Configure al menos un modelo'
|
||||
ErrAgentAccountSingleInitialModel: 'Solo se puede configurar un modelo inicial al crear la cuenta'
|
||||
ErrAgentModelNotInAccount: 'El modelo no está configurado en la cuenta seleccionada'
|
||||
ErrAgentModelInUse: 'Un agente vinculado todavía usa este modelo: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL base requerida'
|
||||
ErrAgentApiKeyRequired: 'Clave API requerida'
|
||||
ErrAgentComposeRequired: 'Contenido compose requerido'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'Error al particionar, {{ .err }}'
|
||||
FormatDiskErr: 'Error al formatear disco, {{ .err }}'
|
||||
MountDiskErr: 'Error al montar disco, {{ .err }}'
|
||||
XfsNotFound: 'No se detectó el sistema de archivos xfs instale xfsprogs primero'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'Comando de riesgo bloqueado: {{ .command }}'
|
||||
TerminalAIThinking: 'La IA está pensando...'
|
||||
TerminalAIReadyToExecute: 'Pensamiento completado, pulsa Enter para ejecutar (duración: {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'La solicitud de AI ha fallado: {{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: '不正なリクエスト: {{ .detail }}'
|
||||
ErrTokenParse: 'トークンエラー: {{ .detail }}'
|
||||
ErrInitialPassword: '元のパスワードが違います'
|
||||
ErrInternalServer: '内部エラー: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'リモートのダウンロード URL が無効か、到達できません:{{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'リモート URL がファイルではなくエラーページ等を返しました、{{ .detail }}'
|
||||
ErrRecordExist: 'レコードが既に存在します'
|
||||
ErrRecordNotFound: 'レコードが見つかりません'
|
||||
ErrStructTransform: '型変換エラー: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'アカウント接続不可: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'エージェントプロバイダ非対応'
|
||||
ErrAgentAccountRequired: 'まずエージェントアカウントを選択'
|
||||
ErrAgentAccountNotVerified: 'アカウント未検証です'
|
||||
ErrAgentProviderMismatch: 'エージェントプロバイダが一致しません'
|
||||
ErrAgentAccountModelsRequired: '少なくとも 1 つのモデルを設定してください'
|
||||
ErrAgentAccountSingleInitialModel: 'アカウント作成時に設定できる初期モデルは 1 つだけです'
|
||||
ErrAgentModelNotInAccount: '選択したモデルはこのアカウントに設定されていません'
|
||||
ErrAgentModelInUse: 'このモデルはまだエージェントで使用中です: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'ベースURLが必要です'
|
||||
ErrAgentApiKeyRequired: 'APIキーが必要です'
|
||||
ErrAgentComposeRequired: 'Composeが必要です'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'パーティションに失敗しました、{{ .err }}'
|
||||
FormatDiskErr: 'ディスクのフォーマットに失敗しました、{{ .err }}'
|
||||
MountDiskErr: 'ディスクのマウントに失敗しました、{{ .err }}'
|
||||
XfsNotFound: 'xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: '危険なコマンドをブロックしました: {{ .command }}'
|
||||
TerminalAIThinking: 'AI が考えています...'
|
||||
TerminalAIReadyToExecute: '思考が完了しました。Enter で実行してください(所要時間 {{ .duration }}、token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'AI リクエストに失敗しました: {{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: '잘못된 요청: {{ .detail }}'
|
||||
ErrTokenParse: '토큰 오류: {{ .detail }}'
|
||||
ErrInitialPassword: '원래 비밀번호가 틀립니다'
|
||||
ErrInternalServer: '서버 오류: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: '원격 다운로드 주소가 잘못되었거나 접근할 수 없습니다: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: '원격 주소가 파일이 아닌 콘텐츠를 반환했습니다 (오류 페이지일 수 있음), {{ .detail }}'
|
||||
ErrRecordExist: '레코드가 이미 있습니다'
|
||||
ErrRecordNotFound: '레코드를 찾을 수 없습니다'
|
||||
ErrStructTransform: '형 변환 실패: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: '계정 연결 불가: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: '지원되지 않는 에이전트 공급자'
|
||||
ErrAgentAccountRequired: '먼저 에이전트 계정을 선택하세요'
|
||||
ErrAgentAccountNotVerified: '에이전트 계정 미확인'
|
||||
ErrAgentProviderMismatch: '에이전트 공급자 불일치'
|
||||
ErrAgentAccountModelsRequired: '최소 한 개의 모델을 구성해 주세요'
|
||||
ErrAgentAccountSingleInitialModel: '계정 생성 시 초기 모델은 하나만 구성할 수 있습니다'
|
||||
ErrAgentModelNotInAccount: '선택한 모델이 현재 계정에 구성되어 있지 않습니다'
|
||||
ErrAgentModelInUse: '이 모델을 아직 사용 중인 에이전트가 있습니다: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: '기본 URL 필요'
|
||||
ErrAgentApiKeyRequired: 'API 키 필요'
|
||||
ErrAgentComposeRequired: 'Compose 내용 필요'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: '파티션 분할에 실패했습니다, {{ .err }}'
|
||||
FormatDiskErr: '디스크 포맷에 실패했습니다, {{ .err }}'
|
||||
MountDiskErr: '디스크 마운트에 실패했습니다, {{ .err }}'
|
||||
XfsNotFound: 'xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: '위험한 명령이 차단되었습니다: {{ .command }}'
|
||||
TerminalAIThinking: 'AI가 생각 중입니다...'
|
||||
TerminalAIReadyToExecute: '생각이 완료되었습니다. Enter를 눌러 실행하세요 (소요 시간 {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'AI 요청 실패: {{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: 'Permintaan tidak sah: {{ .detail }}'
|
||||
ErrTokenParse: 'Token tidak sah: {{ .detail }}'
|
||||
ErrInitialPassword: 'Kata laluan asal salah'
|
||||
ErrInternalServer: 'Ralat pelayan: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'Muat turun jauh gagal atau URL tidak boleh diakses: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'URL jauh tidak mengembalikan fail (mungkin halaman ralat), {{ .detail }}'
|
||||
ErrRecordExist: 'Rekod sudah wujud'
|
||||
ErrRecordNotFound: 'Rekod tidak ditemui'
|
||||
ErrStructTransform: 'Ralat penukaran: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'Sambungan akaun tidak tersedia: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Penyedia ejen tidak disokong'
|
||||
ErrAgentAccountRequired: 'Pilih akaun ejen terlebih dahulu'
|
||||
ErrAgentAccountNotVerified: 'Akaun ejen belum disahkan'
|
||||
ErrAgentProviderMismatch: 'Penyedia ejen tidak sepadan'
|
||||
ErrAgentAccountModelsRequired: 'Sila konfigur sekurang-kurangnya satu model'
|
||||
ErrAgentAccountSingleInitialModel: 'Hanya satu model awal boleh dikonfigurasi semasa mencipta akaun'
|
||||
ErrAgentModelNotInAccount: 'Model tidak dikonfigurasikan dalam akaun yang dipilih'
|
||||
ErrAgentModelInUse: 'Masih ada ejen terikat yang menggunakan model ini: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL asas diperlukan'
|
||||
ErrAgentApiKeyRequired: 'Kunci API diperlukan'
|
||||
ErrAgentComposeRequired: 'Kandungan compose diperlukan'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'Gagal membahagikan, {{ .err }}'
|
||||
FormatDiskErr: 'Gagal memformat cakera, {{ .err }}'
|
||||
MountDiskErr: 'Gagal mengkaitkan cakera, {{ .err }}'
|
||||
XfsNotFound: 'Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'Perintah berisiko telah disekat: {{ .command }}'
|
||||
TerminalAIThinking: 'AI sedang berfikir...'
|
||||
TerminalAIReadyToExecute: 'Pemikiran selesai, tekan Enter untuk jalankan (tempoh: {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'Permintaan AI gagal: {{ .err }}'
|
||||
|
||||
@@ -2,6 +2,8 @@ ErrInvalidParams: 'Requisição inválida: {{ .detail }}'
|
||||
ErrTokenParse: 'Erro de token: {{ .detail }}'
|
||||
ErrInitialPassword: 'Senha original incorreta'
|
||||
ErrInternalServer: 'Erro interno: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'Falha no download remoto ou URL inacessível: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'A URL remota não retornou um arquivo (possivelmente uma página de erro), {{ .detail }}'
|
||||
ErrRecordExist: 'Registro já existe'
|
||||
ErrRecordNotFound: 'Registro não encontrado'
|
||||
ErrStructTransform: 'Erro de conversão: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'Conexão da conta indisponível: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Provedor de agente não suportado'
|
||||
ErrAgentAccountRequired: 'Selecione uma conta de agente primeiro'
|
||||
ErrAgentAccountNotVerified: 'Conta de agente não verificada'
|
||||
ErrAgentProviderMismatch: 'Provedor de agente diferente'
|
||||
ErrAgentAccountModelsRequired: 'Configure pelo menos um modelo'
|
||||
ErrAgentAccountSingleInitialModel: 'Apenas um modelo inicial pode ser configurado ao criar a conta'
|
||||
ErrAgentModelNotInAccount: 'O modelo não está configurado na conta selecionada'
|
||||
ErrAgentModelInUse: 'Ainda há um agente vinculado usando este modelo: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'URL base obrigatória'
|
||||
ErrAgentApiKeyRequired: 'Chave API obrigatória'
|
||||
ErrAgentComposeRequired: 'Conteúdo de compose obrigatório'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'Falha ao particionar, {{ .err }}'
|
||||
FormatDiskErr: 'Falha ao formatar disco, {{ .err }}'
|
||||
MountDiskErr: 'Falha ao montar disco, {{ .err }}'
|
||||
XfsNotFound: 'Sistema de arquivos xfs não detectado instale xfsprogs primeiro'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'Comando de risco bloqueado: {{ .command }}'
|
||||
TerminalAIThinking: 'A IA está pensando...'
|
||||
TerminalAIReadyToExecute: 'Pensamento concluído, pressione Enter para executar (duração: {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'Falha na solicitação de AI: {{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: 'Неверный запрос: {{ .detail }}'
|
||||
ErrTokenParse: 'Ошибка токена: {{ .detail }}'
|
||||
ErrInitialPassword: 'Исходный пароль неверен'
|
||||
ErrInternalServer: 'Внутренняя ошибка: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'Неверный или недоступный URL удалённой загрузки: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'Удалённый сервер вернул не файл (возможно, страницу ошибки), {{ .detail }}'
|
||||
ErrRecordExist: 'Запись уже существует'
|
||||
ErrRecordNotFound: 'Запись не найдена'
|
||||
ErrStructTransform: 'Ошибка преобразования: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'Связь с аккаунтом недоступн
|
||||
ErrAgentProviderNotSupported: 'Провайдер агента не поддерживается'
|
||||
ErrAgentAccountRequired: 'Выберите аккаунт агента'
|
||||
ErrAgentAccountNotVerified: 'Аккаунт не подтверждён'
|
||||
ErrAgentProviderMismatch: 'Провайдер не совпадает'
|
||||
ErrAgentAccountModelsRequired: 'Настройте хотя бы одну модель'
|
||||
ErrAgentAccountSingleInitialModel: 'При создании аккаунта можно указать только одну начальную модель'
|
||||
ErrAgentModelNotInAccount: 'Модель не настроена в выбранном аккаунте'
|
||||
ErrAgentModelInUse: 'Эта модель всё ещё используется агентом: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Нужен базовый URL'
|
||||
ErrAgentApiKeyRequired: 'Нужен API-ключ'
|
||||
ErrAgentComposeRequired: 'Нужен compose'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'Не удалось разделить, {{ .err }}'
|
||||
FormatDiskErr: 'Не удалось отформатировать диск, {{ .err }}'
|
||||
MountDiskErr: 'Не удалось подключить диск, {{ .err }}'
|
||||
XfsNotFound: 'Файловая система xfs не обнаружена, сначала установите xfsprogs'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'Опасная команда заблокирована: {{ .command }}'
|
||||
TerminalAIThinking: 'AI думает...'
|
||||
TerminalAIReadyToExecute: 'Обдумывание завершено, нажмите Enter для выполнения (время: {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'Ошибка запроса AI: {{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: 'İstek geçersiz: {{ .detail }}'
|
||||
ErrTokenParse: 'Token hatası: {{ .detail }}'
|
||||
ErrInitialPassword: 'Orijinal şifre yanlış'
|
||||
ErrInternalServer: 'Sunucu hatası: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: 'Uzak indirme başarısız veya URL erişilemiyor: {{ .detail }}'
|
||||
ErrWgetInvalidContentType: 'Uzak URL bir dosya değil, muhtemelen bir hata sayfası döndürdü, {{ .detail }}'
|
||||
ErrRecordExist: 'Kayıt zaten var'
|
||||
ErrRecordNotFound: 'Kayıt bulunamadı'
|
||||
ErrStructTransform: 'Dönüşüm hatası: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: 'Hesap bağlantısı yok: {{ .err }}'
|
||||
ErrAgentProviderNotSupported: 'Ajans sağlayıcısı desteklenmiyor'
|
||||
ErrAgentAccountRequired: 'Önce bir ajans hesabı seçin'
|
||||
ErrAgentAccountNotVerified: 'Ajans hesabı doğrulanmadı'
|
||||
ErrAgentProviderMismatch: 'Ajans sağlayıcısı eşleşmiyor'
|
||||
ErrAgentAccountModelsRequired: 'Lütfen en az bir model yapılandırın'
|
||||
ErrAgentAccountSingleInitialModel: 'Hesap oluştururken yalnızca bir başlangıç modeli yapılandırılabilir'
|
||||
ErrAgentModelNotInAccount: 'Model seçilen hesapta yapılandırılmadı'
|
||||
ErrAgentModelInUse: 'Bu modeli hâlâ kullanan bağlı bir ajan var: {{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Temel URL gerekli'
|
||||
ErrAgentApiKeyRequired: 'API anahtarı gerekli'
|
||||
ErrAgentComposeRequired: 'Compose içeriği gerekli'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: 'Bölümleme başarısız, {{ .err }}'
|
||||
FormatDiskErr: 'Disk biçimlendirme başarısız, {{ .err }}'
|
||||
MountDiskErr: 'Disk bağlama başarısız, {{ .err }}'
|
||||
XfsNotFound: 'XFS dosya sistemi algılanmadı, lütfen önce xfsprogs''i yükleyin'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: 'Riskli komut engellendi: {{ .command }}'
|
||||
TerminalAIThinking: 'AI dusunuyor...'
|
||||
TerminalAIReadyToExecute: 'Dusunme tamamlandi, calistirmak icin Enter tusuna basin (sure: {{ .duration }}, token: {{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'AI istegi basarisiz oldu: {{ .err }}'
|
||||
|
||||
@@ -2,6 +2,8 @@ ErrInvalidParams: '請求參數錯誤: {{ .detail }}'
|
||||
ErrTokenParse: 'Token 產生錯誤: {{ .detail }}'
|
||||
ErrInitialPassword: '原密碼錯誤'
|
||||
ErrInternalServer: '服務內部錯誤: {{ .detail }}'
|
||||
ErrWgetRemoteFailed: '遠端下載網址無效或無法存取:{{ .detail }}'
|
||||
ErrWgetInvalidContentType: '遠端網址未回傳檔案內容(可能是錯誤頁),{{ .detail }}'
|
||||
ErrRecordExist: '記錄已存在'
|
||||
ErrRecordNotFound: '記錄未能找到'
|
||||
ErrStructTransform: '型別轉換失敗: {{ .err }}'
|
||||
@@ -39,7 +41,10 @@ ErrAgentAccountUnavailable: '帳號連線資訊不可用,錯誤:{{ .err }}
|
||||
ErrAgentProviderNotSupported: '暫不支援該智能體提供商,請重試'
|
||||
ErrAgentAccountRequired: '請選擇智能體帳號後重試'
|
||||
ErrAgentAccountNotVerified: '帳號未驗證通過,請重試'
|
||||
ErrAgentProviderMismatch: '帳號提供商不符,請重試'
|
||||
ErrAgentAccountModelsRequired: '請至少配置一個模型'
|
||||
ErrAgentAccountSingleInitialModel: '建立帳號時只能配置一個初始模型'
|
||||
ErrAgentModelNotInAccount: '所選模型未配置在當前帳號中,請重試'
|
||||
ErrAgentModelInUse: '仍有智能體正在使用該模型:{{ .name }}'
|
||||
ErrAgentBaseURLRequired: 'Base URL 不可為空,請重試'
|
||||
ErrAgentApiKeyRequired: 'API Key 不可為空,請重試'
|
||||
ErrAgentComposeRequired: '自訂編排內容不可為空,請重試'
|
||||
@@ -504,3 +509,9 @@ PartitionDiskErr: '分區失敗,{{ .err }}'
|
||||
FormatDiskErr: '格式化磁碟失敗,{{ .err }}'
|
||||
MountDiskErr: '掛載磁碟失敗,{{ .err }}'
|
||||
XfsNotFound: '未偵測到 xfs 檔案系統,請先安裝 xfsprogs'
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: '已攔截風險命令:{{ .command }}'
|
||||
TerminalAIThinking: 'AI 正在思考...'
|
||||
TerminalAIReadyToExecute: '思考完成,請按 Enter 執行(耗時 {{ .duration }},token:{{ .tokens }})'
|
||||
TerminalAIRequestFailed: 'AI 請求失敗:{{ .err }}'
|
||||
|
||||
+12
-1
@@ -2,6 +2,8 @@ ErrInvalidParams: "参数错误: {{ .detail }}"
|
||||
ErrTokenParse: "Token 错误: {{ .detail }}"
|
||||
ErrInitialPassword: "原密码错误"
|
||||
ErrInternalServer: "服务错误: {{ .detail }}"
|
||||
ErrWgetRemoteFailed: "远程下载地址无效或无法访问:{{ .detail }}"
|
||||
ErrWgetInvalidContentType: "远程地址返回的不是文件内容(可能是错误页),{{ .detail }}"
|
||||
ErrRecordExist: "记录已存在"
|
||||
ErrRecordNotFound: "记录不存在"
|
||||
ErrStructTransform: "类型转换失败: {{ .err }}"
|
||||
@@ -44,7 +46,10 @@ ErrAgentAccountUnavailable: "账号连接信息不可用: {{ .err }}"
|
||||
ErrAgentProviderNotSupported: "不支持该智能体提供商"
|
||||
ErrAgentAccountRequired: "请选择智能体账号后重试"
|
||||
ErrAgentAccountNotVerified: "账号未通过验证"
|
||||
ErrAgentProviderMismatch: "账号提供商不匹配"
|
||||
ErrAgentAccountModelsRequired: "请至少配置一个模型"
|
||||
ErrAgentAccountSingleInitialModel: "创建账号时只能配置一个初始模型"
|
||||
ErrAgentModelNotInAccount: "所选模型未配置在当前账号中"
|
||||
ErrAgentModelInUse: "仍有智能体正在使用该模型: {{ .name }}"
|
||||
ErrAgentBaseURLRequired: "Base URL 不能为空"
|
||||
ErrAgentApiKeyRequired: "API Key 不能为空"
|
||||
ErrAgentComposeRequired: "自定义编排内容不能为空"
|
||||
@@ -562,3 +567,9 @@ PartitionDiskErr: "分区失败,{{ .err }}"
|
||||
FormatDiskErr: "格式化磁盘失败,{{ .err }}"
|
||||
MountDiskErr: "挂载磁盘失败,{{ .err }}"
|
||||
XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs"
|
||||
|
||||
# terminal
|
||||
TerminalAIBlockedRiskyCommand: "已拦截风险命令:{{ .command }}"
|
||||
TerminalAIThinking: "AI 正在思考..."
|
||||
TerminalAIReadyToExecute: "思考完成,请回车执行(耗时 {{ .duration }},token:{{ .tokens }})"
|
||||
TerminalAIRequestFailed: "AI 请求失败:{{ .err }}"
|
||||
|
||||
@@ -65,7 +65,6 @@ func InitAgentDB() {
|
||||
migrations.AddCronjobArgs,
|
||||
migrations.AddWebsiteAcmeAccountColumn,
|
||||
migrations.AddAgentTables,
|
||||
migrations.MigrateOpenclawAgents,
|
||||
migrations.AddAgentCustomModelFields,
|
||||
migrations.AddAppInstallSortOrder,
|
||||
migrations.AddAgentAccountRememberAPIKey,
|
||||
@@ -73,6 +72,11 @@ func InitAgentDB() {
|
||||
migrations.AddAgentTypeForAgents,
|
||||
migrations.NormalizeAgentAccountVerifiedStatus,
|
||||
migrations.NormalizeOllamaAccountAPIType,
|
||||
migrations.InitAgentAccountModelPool,
|
||||
migrations.AddHostTable,
|
||||
migrations.AddAITerminalSettings,
|
||||
migrations.UpdateAgentQuickJumpTitle,
|
||||
migrations.FixOpenclaw20260323HTTPPort,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -52,6 +52,7 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.DatabasePostgresql{},
|
||||
&model.Favorite{},
|
||||
&model.Firewall{},
|
||||
&model.Host{},
|
||||
&model.Ftp{},
|
||||
&model.ImageRepo{},
|
||||
&model.ScriptLibrary{},
|
||||
@@ -642,7 +643,7 @@ var AddAgentQuickJump = &gormigrate.Migration{
|
||||
|
||||
return tx.Create(&model.QuickJump{
|
||||
Name: "Agent",
|
||||
Title: "aiTools.agents.agents",
|
||||
Title: "aiTools.agents.agent",
|
||||
Recommend: 1,
|
||||
IsShow: true,
|
||||
Router: "/ai/agents/agent",
|
||||
@@ -896,114 +897,6 @@ var AddAgentTables = &gormigrate.Migration{
|
||||
},
|
||||
}
|
||||
|
||||
var MigrateOpenclawAgents = &gormigrate.Migration{
|
||||
ID: "20260207-migrate-openclaw-agents",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var installs []model.AppInstall
|
||||
if err := tx.Preload("App").Find(&installs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, install := range installs {
|
||||
appKey := install.App.Key
|
||||
if appKey == "" || install.App.Resource == "" {
|
||||
var app model.App
|
||||
if err := tx.First(&app, install.AppId).Error; err == nil {
|
||||
install.App = app
|
||||
appKey = app.Key
|
||||
}
|
||||
}
|
||||
if appKey != constant.AppOpenclaw {
|
||||
continue
|
||||
}
|
||||
var count int64
|
||||
if err := tx.Model(&model.Agent{}).Where("app_install_id = ?", install.ID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
envMap := map[string]interface{}{}
|
||||
if strings.TrimSpace(install.Env) != "" {
|
||||
_ = json.Unmarshal([]byte(install.Env), &envMap)
|
||||
}
|
||||
configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
|
||||
cfgMeta := migrationutils.OpenclawMeta{}
|
||||
if fileData, err := os.ReadFile(configPath); err == nil {
|
||||
cfgMeta = migrationutils.ParseOpenclawMeta(fileData)
|
||||
}
|
||||
provider := strings.ToLower(migrationutils.GetEnvStr(envMap, "PROVIDER"))
|
||||
if provider == "" {
|
||||
provider = strings.ToLower(cfgMeta.Provider)
|
||||
}
|
||||
if provider == "" {
|
||||
continue
|
||||
}
|
||||
provider = migrationutils.NormalizeOpenclawProvider(provider, cfgMeta.BaseURL)
|
||||
modelName := migrationutils.GetEnvStr(envMap, "MODEL")
|
||||
if modelName == "" {
|
||||
modelName = cfgMeta.Model
|
||||
}
|
||||
baseURL := migrationutils.GetEnvStr(envMap, "BASE_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = cfgMeta.BaseURL
|
||||
}
|
||||
apiKey := migrationutils.GetEnvStr(envMap, "API_KEY")
|
||||
if apiKey == "" {
|
||||
apiKey = cfgMeta.APIKey
|
||||
}
|
||||
token := migrationutils.GetEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
|
||||
if token == "" {
|
||||
token = cfgMeta.Token
|
||||
}
|
||||
if provider != "ollama" {
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := migrationutils.DefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
}
|
||||
if provider == "ollama" && baseURL == "" {
|
||||
continue
|
||||
}
|
||||
var account model.AgentAccount
|
||||
err := tx.Where("provider = ? AND api_key = ? AND base_url = ?", provider, apiKey, baseURL).First(&account).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
account = model.AgentAccount{
|
||||
Provider: provider,
|
||||
Name: install.Name,
|
||||
APIKey: apiKey,
|
||||
BaseURL: baseURL,
|
||||
Verified: apiKey != "" || provider == "ollama",
|
||||
}
|
||||
if err := tx.Create(&account).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
agent := model.Agent{
|
||||
Name: install.Name,
|
||||
Provider: provider,
|
||||
Model: modelName,
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
Token: token,
|
||||
Status: install.Status,
|
||||
Message: install.Message,
|
||||
AppInstallID: install.ID,
|
||||
AccountID: account.ID,
|
||||
ConfigPath: configPath,
|
||||
}
|
||||
if err := tx.Create(&agent).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAgentCustomModelFields = &gormigrate.Migration{
|
||||
ID: "20260224-add-agent-custom-model-fields",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
@@ -1095,3 +988,166 @@ var NormalizeOllamaAccountAPIType = &gormigrate.Migration{
|
||||
Update("api_type", "openai-responses").Error
|
||||
},
|
||||
}
|
||||
|
||||
var InitAgentAccountModelPool = &gormigrate.Migration{
|
||||
ID: "20260319-init-agent-account-model-pool",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.AgentAccountModel{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return migrationutils.MigrateAgentAccountModelPool(tx)
|
||||
},
|
||||
}
|
||||
|
||||
var AddHostTable = &gormigrate.Migration{
|
||||
ID: "20260318-add-host-table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.AutoMigrate(&model.Host{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if global.CoreDB == nil || !global.CoreDB.Migrator().HasTable("hosts") {
|
||||
if err := tx.Create(&model.Group{Name: "Default", Type: "host", IsDefault: true}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var encryptSetting model.Setting
|
||||
if err := global.CoreDB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil {
|
||||
global.LOG.Errorf("failed to get encrypt key from core db, err: %v", err)
|
||||
return nil
|
||||
}
|
||||
coreEncryptKey := strings.TrimSpace(encryptSetting.Value)
|
||||
if coreEncryptKey == "" {
|
||||
global.LOG.Error("encrypt key from core db is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
groupIDMap := make(map[uint]uint)
|
||||
defaultGroupID := uint(0)
|
||||
var coreGroups []model.Group
|
||||
if err := global.CoreDB.Where("type = ?", "host").Order("id asc").Find(&coreGroups).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, coreGroup := range coreGroups {
|
||||
agentGroup := model.Group{
|
||||
Name: coreGroup.Name,
|
||||
Type: "host",
|
||||
IsDefault: coreGroup.IsDefault,
|
||||
}
|
||||
if agentGroup.IsDefault {
|
||||
defaultGroupID = coreGroup.ID
|
||||
}
|
||||
if err := tx.Create(&agentGroup).Error; err != nil {
|
||||
global.LOG.Errorf("failed to create group, group id: %v, err: %v", coreGroup.ID, err)
|
||||
continue
|
||||
}
|
||||
groupIDMap[coreGroup.ID] = agentGroup.ID
|
||||
}
|
||||
|
||||
var coreHosts []model.Host
|
||||
if err := global.CoreDB.Order("id asc").Find(&coreHosts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, coreHost := range coreHosts {
|
||||
password, err := encrypt.StringDecryptWithKey(coreHost.Password, coreEncryptKey)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to decrypt host password, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
privateKey, err := encrypt.StringDecryptWithKey(coreHost.PrivateKey, coreEncryptKey)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to decrypt host private key, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
passPhrase, err := encrypt.StringDecryptWithKey(coreHost.PassPhrase, coreEncryptKey)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to decrypt host pass phrase, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
encryptedPassword, err := encrypt.StringEncrypt(password)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to encrypt host password, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
encryptedPrivateKey, err := encrypt.StringEncrypt(privateKey)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to encrypt host private key, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
encryptedPassPhrase, err := encrypt.StringEncrypt(passPhrase)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("failed to encrypt host pass phrase, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
groupID := defaultGroupID
|
||||
if mappedGroupID, ok := groupIDMap[coreHost.GroupID]; ok && mappedGroupID != 0 {
|
||||
groupID = mappedGroupID
|
||||
}
|
||||
host := model.Host{
|
||||
GroupID: groupID,
|
||||
Name: coreHost.Name,
|
||||
Addr: coreHost.Addr,
|
||||
Port: coreHost.Port,
|
||||
User: coreHost.User,
|
||||
AuthMode: coreHost.AuthMode,
|
||||
Password: encryptedPassword,
|
||||
PrivateKey: encryptedPrivateKey,
|
||||
PassPhrase: encryptedPassPhrase,
|
||||
RememberPassword: coreHost.RememberPassword,
|
||||
Description: coreHost.Description,
|
||||
}
|
||||
if err := tx.Create(&host).Error; err != nil {
|
||||
global.LOG.Errorf("failed to create host, host id: %v, err: %v", coreHost.ID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var AddAITerminalSettings = &gormigrate.Migration{
|
||||
ID: "20260318-add-ai-terminal-settings",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&model.Setting{Key: "AIStatus", Value: constant.StatusDisable}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "AIAccountID", Value: ""}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Create(&model.Setting{Key: "AIPrefix", Value: constant.DefaultTerminalAIPrefix}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(&model.Setting{
|
||||
Key: "AIRiskCommands",
|
||||
Value: constant.DefaultTerminalAIRiskCommands,
|
||||
}).Error
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateAgentQuickJumpTitle = &gormigrate.Migration{
|
||||
ID: "20260324-update-agent-quick-jump-title",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Model(&model.QuickJump{}).
|
||||
Where("title = ?", "aiTools.agents.agents").
|
||||
Update("title", "aiTools.agents.agent").Error
|
||||
},
|
||||
}
|
||||
|
||||
var FixOpenclaw20260323HTTPPort = &gormigrate.Migration{
|
||||
ID: "20260325-fix-openclaw-20260323-http-port",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Exec(
|
||||
`UPDATE app_installs
|
||||
SET http_port = https_port,
|
||||
https_port = 0
|
||||
WHERE version = ?
|
||||
AND https_port > 0
|
||||
AND app_id IN (SELECT id FROM apps WHERE key = ?)`,
|
||||
"2026.3.24",
|
||||
constant.AppOpenclaw,
|
||||
).Error
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"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/app/service"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type legacyAgentAccountModelPoolSource struct {
|
||||
model.AgentAccount
|
||||
LegacyModel string `gorm:"column:model"`
|
||||
LegacyModels string `gorm:"column:models"`
|
||||
}
|
||||
|
||||
func MigrateAgentAccountModelPool(tx *gorm.DB) error {
|
||||
var accounts []legacyAgentAccountModelPoolSource
|
||||
if err := tx.Table(model.AgentAccount{}.TableName()).Find(&accounts).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, account := range accounts {
|
||||
count := int64(0)
|
||||
if err := tx.Model(&model.AgentAccountModel{}).Where("account_id = ?", account.ID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
continue
|
||||
}
|
||||
models, err := buildMigratedAgentAccountModels(tx, &account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(models) == 0 {
|
||||
continue
|
||||
}
|
||||
for index, item := range models {
|
||||
inputPayload, err := json.Marshal(item.Input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
record := &model.AgentAccountModel{
|
||||
AccountID: account.ID,
|
||||
Model: item.ID,
|
||||
Name: item.Name,
|
||||
ContextWindow: item.ContextWindow,
|
||||
MaxTokens: item.MaxTokens,
|
||||
Reasoning: item.Reasoning,
|
||||
Input: string(inputPayload),
|
||||
SortOrder: index + 1,
|
||||
}
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildMigratedAgentAccountModels(tx *gorm.DB, account *legacyAgentAccountModelPoolSource) ([]dto.AgentAccountModel, error) {
|
||||
if account == nil {
|
||||
return nil, nil
|
||||
}
|
||||
baseAccount := account.AgentAccount
|
||||
requested := make([]dto.AgentAccountModel, 0)
|
||||
if strings.TrimSpace(account.LegacyModels) != "" {
|
||||
if err := json.Unmarshal([]byte(account.LegacyModels), &requested); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
seen := make(map[string]struct{}, len(requested))
|
||||
for _, item := range requested {
|
||||
target := strings.TrimSpace(item.ID)
|
||||
if target == "" {
|
||||
continue
|
||||
}
|
||||
seen[target] = struct{}{}
|
||||
}
|
||||
appendModel := func(modelID string) {
|
||||
target := strings.TrimSpace(modelID)
|
||||
if target == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[target]; ok {
|
||||
return
|
||||
}
|
||||
seen[target] = struct{}{}
|
||||
requested = append(requested, dto.AgentAccountModel{ID: target})
|
||||
}
|
||||
appendModel(account.LegacyModel)
|
||||
if account.ID > 0 {
|
||||
var agents []model.Agent
|
||||
if err := tx.Where("account_id = ?", account.ID).Find(&agents).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, agent := range agents {
|
||||
appendModel(agent.Model)
|
||||
}
|
||||
}
|
||||
models, err := service.MergeCatalogAgentAccountModelsForMigration(&baseAccount, requested)
|
||||
if err != nil {
|
||||
if strings.TrimSpace(err.Error()) == "model is required" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
applyLegacyAgentAccountModelDefaults(account.Provider, models)
|
||||
return models, nil
|
||||
}
|
||||
|
||||
func applyLegacyAgentAccountModelDefaults(provider string, models []dto.AgentAccountModel) {
|
||||
if provider != "custom" && provider != "ollama" && provider != "vllm" {
|
||||
return
|
||||
}
|
||||
meta, ok := providercatalog.Get(provider)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for i := range models {
|
||||
if strings.TrimSpace(models[i].Name) == "" {
|
||||
models[i].Name = strings.TrimSpace(models[i].ID)
|
||||
}
|
||||
if models[i].ContextWindow <= 0 {
|
||||
models[i].ContextWindow = meta.Default.ContextWindow
|
||||
}
|
||||
if models[i].MaxTokens <= 0 {
|
||||
models[i].MaxTokens = meta.Default.MaxTokens
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
)
|
||||
|
||||
type OpenclawMeta struct {
|
||||
Provider string
|
||||
Model string
|
||||
BaseURL string
|
||||
APIKey string
|
||||
Token string
|
||||
}
|
||||
|
||||
func GetEnvStr(envMap map[string]interface{}, key string) string {
|
||||
if envMap == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := envMap[key]; ok {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ParseOpenclawMeta(fileData []byte) OpenclawMeta {
|
||||
meta := OpenclawMeta{}
|
||||
content := map[string]interface{}{}
|
||||
if err := json.Unmarshal(fileData, &content); err != nil {
|
||||
return meta
|
||||
}
|
||||
|
||||
meta.Token = getNestedString(content, "gateway", "auth", "token")
|
||||
meta.Model = getNestedString(content, "agents", "defaults", "model", "primary")
|
||||
|
||||
providerKey := ""
|
||||
if parts := strings.SplitN(meta.Model, "/", 2); len(parts) == 2 {
|
||||
providerKey = strings.TrimSpace(parts[0])
|
||||
}
|
||||
|
||||
providers := getNestedMap(content, "models", "providers")
|
||||
providerConfig := map[string]interface{}{}
|
||||
if providerKey != "" {
|
||||
if cfg, ok := providers[providerKey].(map[string]interface{}); ok {
|
||||
providerConfig = cfg
|
||||
}
|
||||
}
|
||||
if len(providerConfig) == 0 && len(providers) == 1 {
|
||||
for key, value := range providers {
|
||||
if cfg, ok := value.(map[string]interface{}); ok {
|
||||
providerKey = key
|
||||
providerConfig = cfg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
meta.Provider = providerKey
|
||||
meta.BaseURL = getString(providerConfig, "baseUrl")
|
||||
meta.APIKey = getString(providerConfig, "apiKey")
|
||||
|
||||
if meta.Model == "" && providerKey != "" {
|
||||
if modelID := getProviderFirstModelID(providerConfig); modelID != "" {
|
||||
meta.Model = providerKey + "/" + modelID
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
func NormalizeOpenclawProvider(provider, baseURL string) string {
|
||||
p := strings.ToLower(strings.TrimSpace(provider))
|
||||
base := strings.ToLower(strings.TrimSpace(baseURL))
|
||||
switch p {
|
||||
case "minimax-portal":
|
||||
return "minimax"
|
||||
case "moonshot":
|
||||
if strings.Contains(base, "moonshot.cn") {
|
||||
return "kimi"
|
||||
}
|
||||
return "moonshot"
|
||||
default:
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultBaseURL(provider string) (string, bool) {
|
||||
return providercatalog.DefaultBaseURL(provider)
|
||||
}
|
||||
|
||||
func getNestedMap(data map[string]interface{}, keys ...string) map[string]interface{} {
|
||||
current := data
|
||||
for _, key := range keys {
|
||||
next, ok := current[key].(map[string]interface{})
|
||||
if !ok {
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
current = next
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func getNestedString(data map[string]interface{}, keys ...string) string {
|
||||
current := data
|
||||
for i, key := range keys {
|
||||
value, ok := current[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if i == len(keys)-1 {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
next, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
current = next
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getString(data map[string]interface{}, key string) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
value, ok := data[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
func getProviderFirstModelID(data map[string]interface{}) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
rawModels, ok := data["models"].([]interface{})
|
||||
if !ok || len(rawModels) == 0 {
|
||||
return ""
|
||||
}
|
||||
first, ok := rawModels[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return getString(first, "id")
|
||||
}
|
||||
@@ -45,10 +45,15 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
|
||||
aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
|
||||
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
|
||||
aiToolsRouter.POST("/agents/overview", baseApi.GetAgentOverview)
|
||||
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
|
||||
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
|
||||
aiToolsRouter.POST("/agents/accounts/update", baseApi.UpdateAgentAccount)
|
||||
aiToolsRouter.POST("/agents/accounts/search", baseApi.PageAgentAccounts)
|
||||
aiToolsRouter.POST("/agents/accounts/models", baseApi.GetAgentAccountModels)
|
||||
aiToolsRouter.POST("/agents/accounts/models/create", baseApi.CreateAgentAccountModel)
|
||||
aiToolsRouter.POST("/agents/accounts/models/update", baseApi.UpdateAgentAccountModel)
|
||||
aiToolsRouter.POST("/agents/accounts/models/delete", baseApi.DeleteAgentAccountModel)
|
||||
aiToolsRouter.POST("/agents/accounts/verify", baseApi.VerifyAgentAccount)
|
||||
aiToolsRouter.POST("/agents/accounts/delete", baseApi.DeleteAgentAccount)
|
||||
aiToolsRouter.POST("/agents/channel/feishu/get", baseApi.GetAgentFeishuConfig)
|
||||
@@ -59,6 +64,9 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/channel/discord/update", baseApi.UpdateAgentDiscordConfig)
|
||||
aiToolsRouter.POST("/agents/channel/wecom/get", baseApi.GetAgentWecomConfig)
|
||||
aiToolsRouter.POST("/agents/channel/wecom/update", baseApi.UpdateAgentWecomConfig)
|
||||
aiToolsRouter.POST("/agents/channel/dingtalk/get", baseApi.GetAgentDingTalkConfig)
|
||||
aiToolsRouter.POST("/agents/channel/dingtalk/update", baseApi.UpdateAgentDingTalkConfig)
|
||||
aiToolsRouter.POST("/agents/channel/weixin/login", baseApi.LoginAgentWeixinChannel)
|
||||
aiToolsRouter.POST("/agents/channel/qqbot/get", baseApi.GetAgentQQBotConfig)
|
||||
aiToolsRouter.POST("/agents/channel/qqbot/update", baseApi.UpdateAgentQQBotConfig)
|
||||
aiToolsRouter.POST("/agents/plugin/install", baseApi.InstallAgentPlugin)
|
||||
@@ -67,6 +75,12 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/agents/security/update", baseApi.UpdateAgentSecurityConfig)
|
||||
aiToolsRouter.POST("/agents/other/get", baseApi.GetAgentOtherConfig)
|
||||
aiToolsRouter.POST("/agents/other/update", baseApi.UpdateAgentOtherConfig)
|
||||
aiToolsRouter.POST("/agents/config-file/get", baseApi.GetAgentConfigFile)
|
||||
aiToolsRouter.POST("/agents/config-file/update", baseApi.UpdateAgentConfigFile)
|
||||
aiToolsRouter.POST("/agents/skills/list", baseApi.ListAgentSkills)
|
||||
aiToolsRouter.POST("/agents/skills/search", baseApi.SearchAgentSkills)
|
||||
aiToolsRouter.POST("/agents/skills/update", baseApi.UpdateAgentSkill)
|
||||
aiToolsRouter.POST("/agents/skills/install", baseApi.InstallAgentSkill)
|
||||
aiToolsRouter.POST("/agents/channel/pairing/approve", baseApi.ApproveAgentChannelPairing)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func (f *FileRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles)
|
||||
fileRouter.POST("/rename", baseApi.ChangeFileName)
|
||||
fileRouter.POST("/wget", baseApi.WgetFile)
|
||||
fileRouter.POST("/wget/stop", baseApi.StopWget)
|
||||
fileRouter.POST("/move", baseApi.MoveFile)
|
||||
fileRouter.GET("/download", baseApi.Download)
|
||||
fileRouter.POST("/chunkdownload", baseApi.DownloadChunkFiles)
|
||||
|
||||
@@ -11,6 +11,16 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter := Router.Group("hosts")
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
hostRouter.POST("", baseApi.CreateHost)
|
||||
hostRouter.POST("/info", baseApi.GetHostByID)
|
||||
hostRouter.POST("/del", baseApi.DeleteHost)
|
||||
hostRouter.POST("/update", baseApi.UpdateHost)
|
||||
hostRouter.POST("/update/group", baseApi.UpdateHostGroup)
|
||||
hostRouter.POST("/search", baseApi.SearchHost)
|
||||
hostRouter.POST("/tree", baseApi.HostTree)
|
||||
hostRouter.POST("/test/byinfo", baseApi.TestByInfo)
|
||||
hostRouter.POST("/test/byid", baseApi.TestByID)
|
||||
|
||||
hostRouter.POST("/firewall/base", baseApi.LoadFirewallBaseInfo)
|
||||
hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule)
|
||||
hostRouter.POST("/firewall/operate", baseApi.OperateFirewall)
|
||||
|
||||
@@ -12,8 +12,10 @@ func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
settingRouter.POST("/search", baseApi.GetSettingInfo)
|
||||
settingRouter.POST("/terminal/ai/search", baseApi.GetTerminalAISettingInfo)
|
||||
settingRouter.GET("/search/available", baseApi.GetSystemAvailable)
|
||||
settingRouter.POST("/update", baseApi.UpdateSetting)
|
||||
settingRouter.POST("/terminal/ai/update", baseApi.UpdateTerminalAISetting)
|
||||
settingRouter.GET("/get/:key", baseApi.GetSettingByKey)
|
||||
|
||||
settingRouter.POST("/description/save", baseApi.SaveDescription)
|
||||
|
||||
@@ -374,7 +374,6 @@ func loadRegistryAuthFromDockerConfig(imageName string) (string, bool) {
|
||||
Username: auth.Username,
|
||||
Password: auth.Password,
|
||||
Auth: auth.Auth,
|
||||
Email: auth.Email,
|
||||
ServerAddress: auth.ServerAddress,
|
||||
IdentityToken: auth.IdentityToken,
|
||||
RegistryToken: auth.RegistryToken,
|
||||
|
||||
+188
-21
@@ -17,6 +17,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
@@ -333,6 +334,17 @@ func (f FileOp) Rename(oldName string, newName string) error {
|
||||
return f.Fs.Rename(oldName, newName)
|
||||
}
|
||||
|
||||
type downloadTask struct {
|
||||
resp *http.Response
|
||||
file *os.File
|
||||
dst string
|
||||
}
|
||||
|
||||
var (
|
||||
downloadMu sync.Mutex
|
||||
downloadTasks = make(map[string]*downloadTask)
|
||||
)
|
||||
|
||||
type WriteCounter struct {
|
||||
Total uint64
|
||||
Written uint64
|
||||
@@ -382,37 +394,83 @@ func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate
|
||||
}
|
||||
}
|
||||
defer client.CloseIdleConnections()
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
return buserr.WithDetail("ErrWgetRemoteFailed", err.Error(), err)
|
||||
}
|
||||
request.Header.Set("Accept-Encoding", "identity")
|
||||
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("get download file [%s] error, err %s", dst, err.Error())
|
||||
return err
|
||||
return buserr.WithDetail("ErrWgetRemoteFailed", err.Error(), err)
|
||||
}
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64*1024))
|
||||
_ = resp.Body.Close()
|
||||
global.LOG.Errorf("wget remote returned non-success status %s for url %s", resp.Status, url)
|
||||
return buserr.WithDetail("ErrWgetRemoteFailed", resp.StatusCode, nil)
|
||||
}
|
||||
|
||||
ct := strings.ToLower(resp.Header.Get("Content-Type"))
|
||||
dstExt := strings.ToLower(filepath.Ext(dst))
|
||||
if (strings.Contains(ct, "text/html") || strings.Contains(ct, "text/xml")) &&
|
||||
dstExt != ".html" && dstExt != ".htm" && dstExt != ".xml" && dstExt != ".svg" {
|
||||
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 64*1024))
|
||||
_ = resp.Body.Close()
|
||||
detail := fmt.Sprintf("Content-Type: %s", ct)
|
||||
global.LOG.Errorf("wget got html/xml response for non-html file %s, url %s, %s", dst, url, detail)
|
||||
return buserr.WithDetail("ErrWgetInvalidContentType", detail, nil)
|
||||
}
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("create download file [%s] error, err %s", dst, err.Error())
|
||||
resp.Body.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
downloadMu.Lock()
|
||||
downloadTasks[key] = &downloadTask{
|
||||
resp: resp,
|
||||
file: out,
|
||||
dst: dst,
|
||||
}
|
||||
downloadMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
out.Close()
|
||||
resp.Body.Close()
|
||||
|
||||
downloadMu.Lock()
|
||||
delete(downloadTasks, key)
|
||||
downloadMu.Unlock()
|
||||
}()
|
||||
|
||||
counter := &WriteCounter{}
|
||||
counter.Key = key
|
||||
if resp.ContentLength > 0 {
|
||||
counter.Total = uint64(resp.ContentLength)
|
||||
}
|
||||
counter.Name = filepath.Base(dst)
|
||||
if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil {
|
||||
|
||||
if _, err := io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil {
|
||||
global.LOG.Errorf("save download file [%s] error, err %s", dst, err.Error())
|
||||
global.CACHE.Del(counter.Key)
|
||||
return
|
||||
}
|
||||
out.Close()
|
||||
resp.Body.Close()
|
||||
|
||||
value := global.CACHE.Get(counter.Key)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
process := &Process{}
|
||||
_ = json.Unmarshal([]byte(value), process)
|
||||
if err := json.Unmarshal([]byte(value), process); err != nil {
|
||||
return
|
||||
}
|
||||
process.Percent = 100
|
||||
process.Name = counter.Name
|
||||
process.Total = process.Written
|
||||
@@ -422,6 +480,25 @@ func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate
|
||||
return nil
|
||||
}
|
||||
|
||||
func CancelDownload(key string) {
|
||||
downloadMu.Lock()
|
||||
task, ok := downloadTasks[key]
|
||||
if !ok {
|
||||
downloadMu.Unlock()
|
||||
return
|
||||
}
|
||||
dst := task.dst
|
||||
downloadMu.Unlock()
|
||||
|
||||
_ = task.file.Close()
|
||||
_ = task.resp.Body.Close()
|
||||
|
||||
if dst != "" {
|
||||
_ = os.Remove(dst)
|
||||
}
|
||||
global.CACHE.Del(key)
|
||||
}
|
||||
|
||||
func (f FileOp) DownloadFile(url, dst string) error {
|
||||
resp, err := req_helper.HandleGet(url)
|
||||
if err != nil {
|
||||
@@ -464,7 +541,7 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error {
|
||||
quotedPaths = append(quotedPaths, fmt.Sprintf("'%s'", p))
|
||||
}
|
||||
mvCommand := fmt.Sprintf("mv %s %s '%s'", coverFlag, strings.Join(quotedPaths, " "), dstPath)
|
||||
if err := cmd.RunDefaultBashC(mvCommand); err != nil {
|
||||
if err := cmd.RunDefaultBashCf(mvCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -527,22 +604,22 @@ func (f FileOp) CopyAndReName(src, dst, name string, cover bool) error {
|
||||
if name != "" && !cover {
|
||||
dstPath = filepath.Join(dst, name)
|
||||
}
|
||||
return cmd.RunDefaultBashCf(`cp -rf '%s' '%s'`, src, dstPath)
|
||||
return cmd.RunDefaultBashCf(`cp -rfp '%s' '%s'`, src, dstPath)
|
||||
} else {
|
||||
dstPath := filepath.Join(dst, name)
|
||||
if cover {
|
||||
dstPath = dst
|
||||
}
|
||||
return cmd.RunDefaultBashCf(`cp -f '%s' '%s'`, src, dstPath)
|
||||
return cmd.RunDefaultBashCf(`cp -fp '%s' '%s'`, src, dstPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (f FileOp) CopyDirWithNewName(src, dst, newName string) error {
|
||||
if newName == "." || newName == "" {
|
||||
return cmd.RunDefaultBashCf(`cp -rf '%s'/. '%s'`, src, dst)
|
||||
return cmd.RunDefaultBashCf(`cp -rfp '%s'/. '%s'`, src, dst)
|
||||
}
|
||||
dstDir := filepath.Join(dst, newName)
|
||||
return cmd.RunDefaultBashCf(`cp -rf '%s' '%s'`, src, dstDir)
|
||||
return cmd.RunDefaultBashCf(`cp -rfp '%s' '%s'`, src, dstDir)
|
||||
}
|
||||
|
||||
func (f FileOp) CopyDir(src, dst string) error {
|
||||
@@ -554,7 +631,7 @@ func (f FileOp) CopyDir(src, dst string) error {
|
||||
if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rf '%s' '%s'`, src, dst+"/")
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rfp '%s' '%s'`, src, dst+"/")
|
||||
}
|
||||
|
||||
func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error {
|
||||
@@ -567,7 +644,7 @@ func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error
|
||||
return err
|
||||
}
|
||||
if len(excludeNames) == 0 {
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rf '%s' '%s'`, src, dst+"/")
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rfp '%s' '%s'`, src, dst+"/")
|
||||
}
|
||||
tmpFiles, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
@@ -600,7 +677,7 @@ func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error
|
||||
|
||||
func (f FileOp) CopyFile(src, dst string) error {
|
||||
dst = filepath.Clean(dst) + string(filepath.Separator)
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -f '%s' '%s'`, src, dst+"/")
|
||||
return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -fp '%s' '%s'`, src, dst+"/")
|
||||
}
|
||||
|
||||
func (f FileOp) GetDirSize(path string) (int64, error) {
|
||||
@@ -797,13 +874,18 @@ func decodeGBK(input string) (string, error) {
|
||||
func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType) error {
|
||||
format := getFormat(cType)
|
||||
if cType == Gz {
|
||||
err := f.DecompressGzFile(srcFile, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := f.tryDecompressTarGz(srcFile, dst, format); err == nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return f.DecompressGzFile(srcFile, dst)
|
||||
}
|
||||
|
||||
type dirEntry struct {
|
||||
path string
|
||||
modTime time.Time
|
||||
}
|
||||
var dirs []dirEntry
|
||||
|
||||
handler := func(ctx context.Context, archFile archiver.File) error {
|
||||
info := archFile.FileInfo
|
||||
if isIgnoreFile(archFile.Name()) {
|
||||
@@ -824,6 +906,7 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
dirs = append(dirs, dirEntry{path: filePath, modTime: info.ModTime()})
|
||||
return nil
|
||||
} else {
|
||||
parentDir := path.Dir(filePath)
|
||||
@@ -846,14 +929,20 @@ func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType
|
||||
if _, err := io.Copy(fw, fr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = os.Chtimes(filePath, info.ModTime(), info.ModTime())
|
||||
return nil
|
||||
}
|
||||
input, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.Extract(context.Background(), input, nil, handler)
|
||||
if err := format.Extract(context.Background(), input, nil, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
_ = os.Chtimes(dirs[i].path, dirs[i].modTime, dirs[i].modTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error {
|
||||
@@ -926,7 +1015,75 @@ func ZipFile(files []archiver.File, dst afero.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) tryDecompressTarGz(srcFile string, dst string, format archiver.CompressedArchive) error {
|
||||
input, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
type dirEntry struct {
|
||||
path string
|
||||
modTime time.Time
|
||||
}
|
||||
var dirs []dirEntry
|
||||
extracted := false
|
||||
|
||||
handler := func(ctx context.Context, archFile archiver.File) error {
|
||||
info := archFile.FileInfo
|
||||
if isIgnoreFile(archFile.Name()) {
|
||||
return nil
|
||||
}
|
||||
filePath := filepath.Join(dst, archFile.NameInArchive)
|
||||
if info.IsDir() {
|
||||
if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
dirs = append(dirs, dirEntry{path: filePath, modTime: info.ModTime()})
|
||||
} else {
|
||||
parentDir := filepath.Dir(filePath)
|
||||
if !f.Stat(parentDir) {
|
||||
if err := f.Fs.MkdirAll(parentDir, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fr, err := archFile.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fr.Close()
|
||||
fw, err := f.Fs.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fw.Close()
|
||||
if _, err := io.Copy(fw, fr); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.Chtimes(filePath, info.ModTime(), info.ModTime())
|
||||
}
|
||||
extracted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := format.Extract(context.Background(), input, nil, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
if !extracted {
|
||||
return fmt.Errorf("no files extracted as tar.gz")
|
||||
}
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
_ = os.Chtimes(dirs[i].path, dirs[i].modTime, dirs[i].modTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
var archiveModTime time.Time
|
||||
if st, err := f.Fs.Stat(srcFile); err == nil {
|
||||
archiveModTime = st.ModTime()
|
||||
}
|
||||
|
||||
in, err := f.Fs.Open(srcFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open source file failed: %w", err)
|
||||
@@ -939,7 +1096,13 @@ func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
}
|
||||
defer gr.Close()
|
||||
|
||||
outName := strings.TrimSuffix(filepath.Base(srcFile), ".gz")
|
||||
outName := ""
|
||||
if gr.Name != "" {
|
||||
outName = filepath.Base(gr.Name)
|
||||
}
|
||||
if outName == "" || outName == "." {
|
||||
outName = strings.TrimSuffix(filepath.Base(srcFile), ".gz")
|
||||
}
|
||||
outPath := filepath.Join(dst, outName)
|
||||
parentDir := filepath.Dir(outPath)
|
||||
if !f.Stat(parentDir) {
|
||||
@@ -958,6 +1121,10 @@ func (f FileOp) DecompressGzFile(srcFile, dst string) error {
|
||||
return fmt.Errorf("copy content failed: %w", err)
|
||||
}
|
||||
|
||||
if !archiveModTime.IsZero() {
|
||||
_ = os.Chtimes(outPath, archiveModTime, archiveModTime)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package components
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
@@ -352,6 +353,7 @@ func (s *Server) UpdateRootLocation() {
|
||||
}
|
||||
|
||||
func (s *Server) UpdateRootProxy(proxy []string) {
|
||||
httpsProxy := len(proxy) > 0 && strings.HasPrefix(strings.ToLower(strings.TrimSpace(proxy[0])), "https://")
|
||||
newDir := Directive{
|
||||
Name: "location",
|
||||
Parameters: []string{"/"},
|
||||
@@ -392,6 +394,14 @@ func (s *Server) UpdateRootProxy(proxy []string) {
|
||||
Name: "proxy_http_version",
|
||||
Parameters: []string{"1.1"},
|
||||
},
|
||||
&Directive{
|
||||
Name: "proxy_ssl_server_name",
|
||||
Parameters: []string{map[bool]string{true: "on", false: "off"}[httpsProxy]},
|
||||
},
|
||||
&Directive{
|
||||
Name: "proxy_ssl_name",
|
||||
Parameters: []string{"$proxy_host"},
|
||||
},
|
||||
&Directive{
|
||||
Name: "proxy_pass",
|
||||
Parameters: proxy,
|
||||
|
||||
@@ -0,0 +1,823 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 30 * time.Second
|
||||
defaultUserAgent = "1panel-terminal-ai/1.0"
|
||||
defaultAnthropicToken = 1024
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
ChatCompletion(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error)
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
Provider string
|
||||
BaseURL string
|
||||
APIKey string
|
||||
Model string
|
||||
APIType string
|
||||
MaxTokens int
|
||||
Timeout time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
type GeneratorConfig struct {
|
||||
Provider string
|
||||
BaseURL string
|
||||
APIKey string
|
||||
Model string
|
||||
APIType string
|
||||
MaxTokens int
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type ChatCompletionRequest struct {
|
||||
Messages []ChatMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
}
|
||||
|
||||
type ThinkingMode struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ChatCompletionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Content string `json:"content"`
|
||||
RawText string `json:"rawText"`
|
||||
Usage ResponseUsage `json:"usage"`
|
||||
}
|
||||
|
||||
type ResponseUsage struct {
|
||||
PromptTokens int `json:"promptTokens"`
|
||||
CompletionTokens int `json:"completionTokens"`
|
||||
TotalTokens int `json:"totalTokens"`
|
||||
}
|
||||
|
||||
type terminalAIClient struct {
|
||||
config ClientConfig
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewClient(cfg ClientConfig) (Client, error) {
|
||||
providerKey := strings.ToLower(strings.TrimSpace(cfg.Provider))
|
||||
if providerKey == "" {
|
||||
providerKey = "custom"
|
||||
}
|
||||
if strings.TrimSpace(cfg.Model) == "" {
|
||||
return nil, fmt.Errorf("model is required")
|
||||
}
|
||||
if strings.TrimSpace(cfg.APIKey) == "" && providerKey != "ollama" {
|
||||
return nil, fmt.Errorf("api key is required")
|
||||
}
|
||||
baseURL := normalizeBaseURL(providerKey, cfg.BaseURL)
|
||||
if baseURL == "" {
|
||||
return nil, fmt.Errorf("base url is required")
|
||||
}
|
||||
cfg.Provider = providerKey
|
||||
cfg.BaseURL = baseURL
|
||||
cfg.APIType = normalizeClientAPIType(providerKey, cfg.APIType)
|
||||
if cfg.Timeout <= 0 {
|
||||
cfg.Timeout = defaultTimeout
|
||||
}
|
||||
client := cfg.HTTPClient
|
||||
if client == nil {
|
||||
client = &http.Client{Timeout: cfg.Timeout}
|
||||
}
|
||||
return &terminalAIClient{
|
||||
config: cfg,
|
||||
httpClient: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ClientOption func(*ClientConfig)
|
||||
|
||||
func WithBaseURL(baseURL string) ClientOption {
|
||||
return func(cfg *ClientConfig) {
|
||||
cfg.BaseURL = baseURL
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) ClientOption {
|
||||
return func(cfg *ClientConfig) {
|
||||
cfg.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithHTTPClient(client *http.Client) ClientOption {
|
||||
return func(cfg *ClientConfig) {
|
||||
cfg.HTTPClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) ChatCompletion(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
if len(req.Messages) == 0 {
|
||||
return nil, fmt.Errorf("messages are required")
|
||||
}
|
||||
switch c.config.APIType {
|
||||
case "anthropic-messages":
|
||||
return c.chatCompletionAnthropic(ctx, req)
|
||||
case "gemini-generate-content":
|
||||
return c.chatCompletionGemini(ctx, req)
|
||||
case "openai-responses":
|
||||
return c.chatCompletionOpenAIResponses(ctx, req)
|
||||
default:
|
||||
return c.chatCompletionOpenAI(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) chatCompletionOpenAI(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
payload := openAIChatCompletionRequest{
|
||||
Model: normalizeModelID(c.config.Model),
|
||||
Messages: req.Messages,
|
||||
MaxTokens: firstPositive(req.MaxTokens, c.config.MaxTokens),
|
||||
Temperature: req.Temperature,
|
||||
TopP: req.TopP,
|
||||
Thinking: thinkingModeForModel(c.config.Model),
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, buildChatCompletionsURL(c.config.BaseURL), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.config.APIKey))
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
httpReq.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
respBody, err := c.do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var completionResp openAIChatCompletionResponse
|
||||
if err := json.Unmarshal(respBody, &completionResp); err != nil {
|
||||
return nil, fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
content := extractAssistantContent(completionResp)
|
||||
return &ChatCompletionResponse{
|
||||
ID: completionResp.ID,
|
||||
Model: firstNonEmptyString(c.config.Model, completionResp.Model),
|
||||
Content: content,
|
||||
RawText: strings.TrimSpace(string(respBody)),
|
||||
Usage: ResponseUsage{
|
||||
PromptTokens: completionResp.Usage.PromptTokens,
|
||||
CompletionTokens: completionResp.Usage.CompletionTokens,
|
||||
TotalTokens: completionResp.Usage.TotalTokens,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) chatCompletionOpenAIResponses(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
payload := openAIResponsesRequest{
|
||||
Model: normalizeModelID(c.config.Model),
|
||||
Input: toResponsesInput(req.Messages),
|
||||
MaxOutputTokens: firstPositive(req.MaxTokens, c.config.MaxTokens),
|
||||
Temperature: req.Temperature,
|
||||
TopP: req.TopP,
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, buildResponsesURL(c.config.BaseURL), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
if strings.TrimSpace(c.config.APIKey) != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+strings.TrimSpace(c.config.APIKey))
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
httpReq.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
respBody, err := c.do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var completionResp openAIResponsesResponse
|
||||
if err := json.Unmarshal(respBody, &completionResp); err != nil {
|
||||
return nil, fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
content := extractResponsesContent(completionResp)
|
||||
return &ChatCompletionResponse{
|
||||
ID: completionResp.ID,
|
||||
Model: firstNonEmptyString(c.config.Model, completionResp.Model),
|
||||
Content: content,
|
||||
RawText: strings.TrimSpace(string(respBody)),
|
||||
Usage: ResponseUsage{
|
||||
PromptTokens: completionResp.Usage.InputTokens,
|
||||
CompletionTokens: completionResp.Usage.OutputTokens,
|
||||
TotalTokens: completionResp.Usage.TotalTokens,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) chatCompletionAnthropic(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
system, messages := toAnthropicMessages(req.Messages)
|
||||
payload := anthropicMessagesRequest{
|
||||
Model: normalizeModelID(c.config.Model),
|
||||
System: system,
|
||||
Messages: messages,
|
||||
MaxTokens: firstPositive(req.MaxTokens, c.config.MaxTokens, defaultAnthropicToken),
|
||||
Temperature: req.Temperature,
|
||||
TopP: req.TopP,
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, buildAnthropicMessagesURL(c.config.BaseURL), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("x-api-key", strings.TrimSpace(c.config.APIKey))
|
||||
httpReq.Header.Set("anthropic-version", "2023-06-01")
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
httpReq.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
respBody, err := c.do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var completionResp anthropicMessagesResponse
|
||||
if err := json.Unmarshal(respBody, &completionResp); err != nil {
|
||||
return nil, fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
content := extractAnthropicContent(completionResp)
|
||||
return &ChatCompletionResponse{
|
||||
ID: completionResp.ID,
|
||||
Model: firstNonEmptyString(c.config.Model, completionResp.Model),
|
||||
Content: content,
|
||||
RawText: strings.TrimSpace(string(respBody)),
|
||||
Usage: ResponseUsage{
|
||||
PromptTokens: completionResp.Usage.InputTokens,
|
||||
CompletionTokens: completionResp.Usage.OutputTokens,
|
||||
TotalTokens: completionResp.Usage.InputTokens + completionResp.Usage.OutputTokens,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) chatCompletionGemini(ctx context.Context, req ChatCompletionRequest) (*ChatCompletionResponse, error) {
|
||||
payload := geminiGenerateContentRequest{
|
||||
Contents: toGeminiContents(req.Messages),
|
||||
GenerationConfig: &geminiGenerationConfig{
|
||||
MaxOutputTokens: firstPositive(req.MaxTokens, c.config.MaxTokens),
|
||||
Temperature: req.Temperature,
|
||||
TopP: req.TopP,
|
||||
},
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal request: %w", err)
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, buildGeminiGenerateContentURL(c.config.BaseURL, normalizeModelID(c.config.Model)), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("x-goog-api-key", strings.TrimSpace(c.config.APIKey))
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
httpReq.Header.Set("User-Agent", defaultUserAgent)
|
||||
|
||||
respBody, err := c.do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var completionResp geminiGenerateContentResponse
|
||||
if err := json.Unmarshal(respBody, &completionResp); err != nil {
|
||||
return nil, fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
content := extractGeminiContent(completionResp)
|
||||
return &ChatCompletionResponse{
|
||||
Model: firstNonEmptyString(c.config.Model, completionResp.ModelVersion),
|
||||
Content: content,
|
||||
RawText: strings.TrimSpace(string(respBody)),
|
||||
Usage: ResponseUsage{
|
||||
PromptTokens: completionResp.Usage.PromptTokenCount,
|
||||
CompletionTokens: completionResp.Usage.CandidatesTokenCount,
|
||||
TotalTokens: completionResp.Usage.TotalTokenCount,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) do(httpReq *http.Request) ([]byte, error) {
|
||||
start := time.Now()
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
c.logRequestCost(httpReq, 0, time.Since(start), err)
|
||||
return nil, fmt.Errorf("do request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.logRequestCost(httpReq, resp.StatusCode, time.Since(start), err)
|
||||
return nil, fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
providerErr := parseProviderError(resp.StatusCode, respBody)
|
||||
c.logRequestCost(httpReq, resp.StatusCode, time.Since(start), providerErr)
|
||||
return nil, providerErr
|
||||
}
|
||||
c.logRequestCost(httpReq, resp.StatusCode, time.Since(start), nil)
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (c *terminalAIClient) logRequestCost(httpReq *http.Request, statusCode int, duration time.Duration, err error) {
|
||||
if global.LOG == nil || httpReq == nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
global.LOG.Warnf("[terminal-ai] request finished, provider=%s model=%s apiType=%s method=%s url=%s status=%d duration=%s err=%v",
|
||||
c.config.Provider,
|
||||
c.config.Model,
|
||||
c.config.APIType,
|
||||
httpReq.Method,
|
||||
httpReq.URL.String(),
|
||||
statusCode,
|
||||
duration,
|
||||
err,
|
||||
)
|
||||
return
|
||||
}
|
||||
global.LOG.Infof("[terminal-ai] request finished, provider=%s model=%s apiType=%s method=%s url=%s status=%d duration=%s",
|
||||
c.config.Provider,
|
||||
c.config.Model,
|
||||
c.config.APIType,
|
||||
httpReq.Method,
|
||||
httpReq.URL.String(),
|
||||
statusCode,
|
||||
duration,
|
||||
)
|
||||
}
|
||||
|
||||
type openAIChatCompletionRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []ChatMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
Thinking *ThinkingMode `json:"thinking,omitempty"`
|
||||
}
|
||||
|
||||
type openAIChatCompletionResponse struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Choices []struct {
|
||||
Message ChatMessage `json:"message"`
|
||||
Text string `json:"text"`
|
||||
} `json:"choices"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type openAIResponsesRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input []openAIResponsesInput `json:"input"`
|
||||
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
}
|
||||
|
||||
type openAIResponsesInput struct {
|
||||
Role string `json:"role"`
|
||||
Content []openAIResponsesInputPart `json:"content"`
|
||||
}
|
||||
|
||||
type openAIResponsesInputPart struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type openAIResponsesResponse struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Output []struct {
|
||||
Content []struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
} `json:"output"`
|
||||
OutputText string `json:"output_text"`
|
||||
Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type anthropicMessagesRequest struct {
|
||||
Model string `json:"model"`
|
||||
System string `json:"system,omitempty"`
|
||||
Messages []anthropicMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"top_p,omitempty"`
|
||||
}
|
||||
|
||||
type anthropicMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type anthropicMessagesResponse struct {
|
||||
ID string `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Content []struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
type geminiGenerateContentRequest struct {
|
||||
Contents []geminiContent `json:"contents"`
|
||||
GenerationConfig *geminiGenerationConfig `json:"generationConfig,omitempty"`
|
||||
}
|
||||
|
||||
type geminiContent struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Parts []geminiContentPart `json:"parts"`
|
||||
}
|
||||
|
||||
type geminiContentPart struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type geminiGenerationConfig struct {
|
||||
MaxOutputTokens int `json:"maxOutputTokens,omitempty"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
TopP *float64 `json:"topP,omitempty"`
|
||||
}
|
||||
|
||||
type geminiGenerateContentResponse struct {
|
||||
Candidates []struct {
|
||||
Content geminiContent `json:"content"`
|
||||
} `json:"candidates"`
|
||||
ModelVersion string `json:"modelVersion"`
|
||||
Usage struct {
|
||||
PromptTokenCount int `json:"promptTokenCount"`
|
||||
CandidatesTokenCount int `json:"candidatesTokenCount"`
|
||||
TotalTokenCount int `json:"totalTokenCount"`
|
||||
} `json:"usageMetadata"`
|
||||
}
|
||||
|
||||
type openAIErrorResponse struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
Type string `json:"type"`
|
||||
Code string `json:"code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func normalizeBaseURL(provider, rawBaseURL string) string {
|
||||
baseURL := strings.TrimSpace(rawBaseURL)
|
||||
if baseURL == "" {
|
||||
defaultBaseURL, ok := providercatalog.DefaultBaseURL(provider)
|
||||
if ok {
|
||||
baseURL = defaultBaseURL
|
||||
}
|
||||
}
|
||||
return strings.TrimRight(baseURL, "/")
|
||||
}
|
||||
|
||||
func buildChatCompletionsURL(baseURL string) string {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
if strings.HasSuffix(baseURL, "/chat/completions") {
|
||||
return baseURL
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return baseURL + "/chat/completions"
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(parsed.Path, "/v1"):
|
||||
parsed.Path += "/chat/completions"
|
||||
case strings.HasSuffix(parsed.Path, "/v1beta"):
|
||||
parsed.Path += "/chat/completions"
|
||||
default:
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/") + "/v1/chat/completions"
|
||||
}
|
||||
return strings.TrimRight(parsed.String(), "/")
|
||||
}
|
||||
|
||||
func buildResponsesURL(baseURL string) string {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
if strings.HasSuffix(baseURL, "/responses") {
|
||||
return baseURL
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return baseURL + "/responses"
|
||||
}
|
||||
if strings.HasSuffix(parsed.Path, "/v1") {
|
||||
parsed.Path += "/responses"
|
||||
} else {
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/") + "/v1/responses"
|
||||
}
|
||||
return strings.TrimRight(parsed.String(), "/")
|
||||
}
|
||||
|
||||
func buildAnthropicMessagesURL(baseURL string) string {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
if strings.HasSuffix(baseURL, "/messages") {
|
||||
return baseURL
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return baseURL + "/messages"
|
||||
}
|
||||
if strings.HasSuffix(parsed.Path, "/v1") {
|
||||
parsed.Path += "/messages"
|
||||
} else {
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/") + "/v1/messages"
|
||||
}
|
||||
return strings.TrimRight(parsed.String(), "/")
|
||||
}
|
||||
|
||||
func buildGeminiGenerateContentURL(baseURL, model string) string {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
model = strings.TrimSpace(model)
|
||||
if model == "" {
|
||||
model = "gemini-3-flash-preview"
|
||||
}
|
||||
parsed, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return strings.TrimRight(baseURL, "/") + "/v1beta/models/" + model + ":generateContent"
|
||||
}
|
||||
if strings.Contains(parsed.Path, "/models/") && strings.HasSuffix(parsed.Path, ":generateContent") {
|
||||
return parsed.String()
|
||||
}
|
||||
if strings.Contains(parsed.Path, "/v1beta") {
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/") + "/models/" + model + ":generateContent"
|
||||
} else {
|
||||
parsed.Path = strings.TrimRight(parsed.Path, "/") + "/v1beta/models/" + model + ":generateContent"
|
||||
}
|
||||
return parsed.String()
|
||||
}
|
||||
|
||||
func normalizeModelID(model string) string {
|
||||
model = strings.TrimSpace(model)
|
||||
if parts := strings.SplitN(model, "/", 2); len(parts) == 2 {
|
||||
return parts[1]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
func thinkingModeForModel(model string) *ThinkingMode {
|
||||
if strings.EqualFold(normalizeModelID(model), "kimi-k2.5") {
|
||||
return &ThinkingMode{Type: "disabled"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeClientAPIType(provider, apiType string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(provider)) {
|
||||
case "anthropic", "kimi-coding", "minimax":
|
||||
return "anthropic-messages"
|
||||
case "gemini":
|
||||
return "gemini-generate-content"
|
||||
case "ollama":
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "openai-completions" {
|
||||
return trim
|
||||
}
|
||||
return "openai-responses"
|
||||
default:
|
||||
trim := strings.ToLower(strings.TrimSpace(apiType))
|
||||
if trim == "" {
|
||||
return "openai-completions"
|
||||
}
|
||||
return trim
|
||||
}
|
||||
}
|
||||
|
||||
func extractAssistantContent(resp openAIChatCompletionResponse) string {
|
||||
if len(resp.Choices) == 0 {
|
||||
return ""
|
||||
}
|
||||
if content := strings.TrimSpace(resp.Choices[0].Message.Content); content != "" {
|
||||
return content
|
||||
}
|
||||
return strings.TrimSpace(resp.Choices[0].Text)
|
||||
}
|
||||
|
||||
func extractResponsesContent(resp openAIResponsesResponse) string {
|
||||
if text := strings.TrimSpace(resp.OutputText); text != "" {
|
||||
return text
|
||||
}
|
||||
for _, item := range resp.Output {
|
||||
for _, content := range item.Content {
|
||||
if strings.TrimSpace(content.Text) != "" {
|
||||
return strings.TrimSpace(content.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractAnthropicContent(resp anthropicMessagesResponse) string {
|
||||
var parts []string
|
||||
for _, item := range resp.Content {
|
||||
if strings.TrimSpace(item.Text) == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, strings.TrimSpace(item.Text))
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n"))
|
||||
}
|
||||
|
||||
func extractGeminiContent(resp geminiGenerateContentResponse) string {
|
||||
if len(resp.Candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
var parts []string
|
||||
for _, part := range resp.Candidates[0].Content.Parts {
|
||||
if strings.TrimSpace(part.Text) == "" {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, strings.TrimSpace(part.Text))
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n"))
|
||||
}
|
||||
|
||||
func toResponsesInput(messages []ChatMessage) []openAIResponsesInput {
|
||||
result := make([]openAIResponsesInput, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
if strings.TrimSpace(message.Content) == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, openAIResponsesInput{
|
||||
Role: normalizeRole(message.Role),
|
||||
Content: []openAIResponsesInputPart{{
|
||||
Type: "input_text",
|
||||
Text: message.Content,
|
||||
}},
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAnthropicMessages(messages []ChatMessage) (string, []anthropicMessage) {
|
||||
var systemParts []string
|
||||
result := make([]anthropicMessage, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
content := strings.TrimSpace(message.Content)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
role := normalizeRole(message.Role)
|
||||
if role == "system" {
|
||||
systemParts = append(systemParts, content)
|
||||
continue
|
||||
}
|
||||
if role != "assistant" {
|
||||
role = "user"
|
||||
}
|
||||
result = append(result, anthropicMessage{
|
||||
Role: role,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
if len(result) == 0 && len(systemParts) > 0 {
|
||||
result = append(result, anthropicMessage{
|
||||
Role: "user",
|
||||
Content: "Generate one shell command only.",
|
||||
})
|
||||
}
|
||||
return strings.Join(systemParts, "\n\n"), result
|
||||
}
|
||||
|
||||
func toGeminiContents(messages []ChatMessage) []geminiContent {
|
||||
result := make([]geminiContent, 0, len(messages))
|
||||
for _, message := range messages {
|
||||
content := strings.TrimSpace(message.Content)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
role := normalizeRole(message.Role)
|
||||
if role == "assistant" {
|
||||
role = "model"
|
||||
} else {
|
||||
role = "user"
|
||||
}
|
||||
result = append(result, geminiContent{
|
||||
Role: role,
|
||||
Parts: []geminiContentPart{{
|
||||
Text: content,
|
||||
}},
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeRole(role string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(role)) {
|
||||
case "assistant", "model":
|
||||
return "assistant"
|
||||
case "system":
|
||||
return "system"
|
||||
default:
|
||||
return "user"
|
||||
}
|
||||
}
|
||||
|
||||
func parseProviderError(statusCode int, body []byte) error {
|
||||
var errResp openAIErrorResponse
|
||||
if err := json.Unmarshal(body, &errResp); err == nil && strings.TrimSpace(errResp.Error.Message) != "" {
|
||||
if strings.TrimSpace(errResp.Error.Code) != "" {
|
||||
return fmt.Errorf("provider returned %d: %s (%s)", statusCode, errResp.Error.Message, errResp.Error.Code)
|
||||
}
|
||||
return fmt.Errorf("provider returned %d: %s", statusCode, errResp.Error.Message)
|
||||
}
|
||||
|
||||
var generic map[string]interface{}
|
||||
if err := json.Unmarshal(body, &generic); err == nil {
|
||||
if message := extractErrorMessage(generic); message != "" {
|
||||
return fmt.Errorf("provider returned %d: %s", statusCode, message)
|
||||
}
|
||||
}
|
||||
|
||||
message := strings.TrimSpace(string(body))
|
||||
if message == "" {
|
||||
message = http.StatusText(statusCode)
|
||||
}
|
||||
if !utf8.ValidString(message) {
|
||||
message = http.StatusText(statusCode)
|
||||
}
|
||||
return fmt.Errorf("provider returned %d: %s", statusCode, message)
|
||||
}
|
||||
|
||||
func extractErrorMessage(value map[string]interface{}) string {
|
||||
if errorValue, ok := value["error"]; ok {
|
||||
switch typed := errorValue.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(typed)
|
||||
case map[string]interface{}:
|
||||
if msg, ok := typed["message"].(string); ok {
|
||||
return strings.TrimSpace(msg)
|
||||
}
|
||||
if msg, ok := typed["status"].(string); ok {
|
||||
return strings.TrimSpace(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if msg, ok := value["message"].(string); ok {
|
||||
return strings.TrimSpace(msg)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstPositive(values ...int) int {
|
||||
for _, value := range values {
|
||||
if value > 0 {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func firstNonEmptyString(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CommandGenerator struct {
|
||||
client Client
|
||||
}
|
||||
|
||||
type CommandGenerateRequest struct {
|
||||
Input string
|
||||
Shell string
|
||||
WorkingDir string
|
||||
OS string
|
||||
RecentCommands []string
|
||||
DirectoryHints []string
|
||||
}
|
||||
|
||||
type CommandGenerateResponse struct {
|
||||
Command string
|
||||
Model string
|
||||
Provider string
|
||||
RawText string
|
||||
Usage ResponseUsage
|
||||
}
|
||||
|
||||
func NewCommandGeneratorFromConfig(cfg GeneratorConfig) (*CommandGenerator, error) {
|
||||
client, err := NewClient(ClientConfig{
|
||||
Provider: cfg.Provider,
|
||||
BaseURL: cfg.BaseURL,
|
||||
APIKey: cfg.APIKey,
|
||||
Model: cfg.Model,
|
||||
APIType: cfg.APIType,
|
||||
MaxTokens: cfg.MaxTokens,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCommandGenerator(client)
|
||||
}
|
||||
|
||||
func NewCommandGenerator(client Client) (*CommandGenerator, error) {
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("client is required")
|
||||
}
|
||||
return &CommandGenerator{client: client}, nil
|
||||
}
|
||||
|
||||
func (g *CommandGenerator) Generate(ctx context.Context, req CommandGenerateRequest) (*CommandGenerateResponse, error) {
|
||||
if strings.TrimSpace(req.Input) == "" {
|
||||
return nil, fmt.Errorf("input is required")
|
||||
}
|
||||
|
||||
resp, err := g.client.ChatCompletion(ctx, ChatCompletionRequest{
|
||||
Messages: []ChatMessage{
|
||||
{Role: "system", Content: buildCommandSystemPrompt()},
|
||||
{Role: "user", Content: buildCommandUserPrompt(req)},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command := sanitizeCommand(resp.Content)
|
||||
if command == "" {
|
||||
return nil, fmt.Errorf("model returned empty command")
|
||||
}
|
||||
|
||||
return &CommandGenerateResponse{
|
||||
Command: command,
|
||||
Model: resp.Model,
|
||||
Provider: providerNameFromModel(resp.Model),
|
||||
RawText: resp.RawText,
|
||||
Usage: resp.Usage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildCommandSystemPrompt() string {
|
||||
return strings.Join([]string{
|
||||
"You are a shell command generator.",
|
||||
"Return exactly one command suitable for direct execution in the user's shell.",
|
||||
"Do not include markdown, code fences, explanations, numbering, comments, or backticks.",
|
||||
"If multiple commands are required, join them with shell operators in a single line.",
|
||||
"Prefer safe, non-destructive commands unless the user explicitly asks for destructive behavior.",
|
||||
"Preserve the user's language when filenames or arguments are ambiguous, but output only the command.",
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func buildCommandUserPrompt(req CommandGenerateRequest) string {
|
||||
var sections []string
|
||||
sections = append(sections, "Task:\n"+strings.TrimSpace(req.Input))
|
||||
|
||||
var env []string
|
||||
if shell := strings.TrimSpace(req.Shell); shell != "" {
|
||||
env = append(env, "Shell: "+shell)
|
||||
}
|
||||
if wd := strings.TrimSpace(req.WorkingDir); wd != "" {
|
||||
env = append(env, "Working directory: "+wd)
|
||||
}
|
||||
if osName := strings.TrimSpace(req.OS); osName != "" {
|
||||
env = append(env, "Operating system: "+osName)
|
||||
}
|
||||
if len(env) > 0 {
|
||||
sections = append(sections, "Environment:\n"+strings.Join(env, "\n"))
|
||||
}
|
||||
|
||||
if block := formatBulletBlock(req.DirectoryHints); block != "" {
|
||||
sections = append(sections, "Directory hints:\n"+block)
|
||||
}
|
||||
if block := formatBulletBlock(req.RecentCommands); block != "" {
|
||||
sections = append(sections, "Recent commands:\n"+block)
|
||||
}
|
||||
|
||||
sections = append(sections, "Output requirement:\nReturn one shell command only.")
|
||||
return strings.Join(sections, "\n\n")
|
||||
}
|
||||
|
||||
func formatBulletBlock(values []string) string {
|
||||
var lines []string
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, "- "+value)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func sanitizeCommand(raw string) string {
|
||||
command := strings.TrimSpace(raw)
|
||||
if command == "" {
|
||||
return ""
|
||||
}
|
||||
command = strings.TrimPrefix(command, "```sh")
|
||||
command = strings.TrimPrefix(command, "```bash")
|
||||
command = strings.TrimPrefix(command, "```zsh")
|
||||
command = strings.TrimPrefix(command, "```shell")
|
||||
command = strings.TrimPrefix(command, "```")
|
||||
command = strings.TrimSuffix(command, "```")
|
||||
command = strings.TrimSpace(command)
|
||||
|
||||
lines := strings.Split(command, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(line), "command:") {
|
||||
line = strings.TrimSpace(line[len("command:"):])
|
||||
}
|
||||
return strings.Trim(line, "` ")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func providerNameFromModel(model string) string {
|
||||
model = strings.TrimSpace(model)
|
||||
if model == "" {
|
||||
return ""
|
||||
}
|
||||
if parts := strings.SplitN(model, "/", 2); len(parts) == 2 {
|
||||
return parts[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var agentAccountRepo = repo.NewIAgentAccountRepo()
|
||||
var agentAccountModelRepo = repo.NewIAgentAccountModelRepo()
|
||||
var terminalRuntimeVersion atomic.Uint64
|
||||
|
||||
type TerminalRuntimeSettings struct {
|
||||
AccountID uint
|
||||
Prefix string
|
||||
RiskCommands []string
|
||||
}
|
||||
|
||||
func CurrentTerminalRuntimeVersion() uint64 {
|
||||
return terminalRuntimeVersion.Load()
|
||||
}
|
||||
|
||||
func InvalidateTerminalRuntimeCache() {
|
||||
terminalRuntimeVersion.Add(1)
|
||||
}
|
||||
|
||||
func ResolveGeneratorConfig(accountID uint) (GeneratorConfig, time.Duration, error) {
|
||||
account, err := loadAgentAccount(accountID)
|
||||
if err != nil {
|
||||
return GeneratorConfig{}, 0, err
|
||||
}
|
||||
|
||||
provider := strings.ToLower(strings.TrimSpace(account.Provider))
|
||||
if provider == "" {
|
||||
return GeneratorConfig{}, 0, fmt.Errorf("agent account provider is required")
|
||||
}
|
||||
model, maxTokens, err := resolveAccountModelConfig(account.ID, provider)
|
||||
if err != nil {
|
||||
return GeneratorConfig{}, 0, err
|
||||
}
|
||||
baseURL := strings.TrimSpace(account.BaseURL)
|
||||
if baseURL == "" {
|
||||
if defaultURL, ok := providercatalog.DefaultBaseURL(provider); ok {
|
||||
baseURL = defaultURL
|
||||
}
|
||||
}
|
||||
apiKey := strings.TrimSpace(account.APIKey)
|
||||
if apiKey == "" {
|
||||
apiKey = lookupProviderAPIKey(provider)
|
||||
}
|
||||
if apiKey == "" && provider != "ollama" {
|
||||
return GeneratorConfig{}, 0, fmt.Errorf("agent account api key is required")
|
||||
}
|
||||
return GeneratorConfig{
|
||||
Provider: provider,
|
||||
BaseURL: baseURL,
|
||||
APIKey: strings.TrimSpace(apiKey),
|
||||
Model: model,
|
||||
APIType: strings.TrimSpace(account.APIType),
|
||||
MaxTokens: maxTokens,
|
||||
}, 30 * time.Second, nil
|
||||
}
|
||||
|
||||
func lookupProviderAPIKey(provider string) string {
|
||||
envKey := providercatalog.EnvKey(provider)
|
||||
if envKey == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(os.Getenv(envKey))
|
||||
}
|
||||
|
||||
func defaultModelForProvider(provider string) (string, int) {
|
||||
meta, ok := providercatalog.Get(provider)
|
||||
if !ok || len(meta.Models) == 0 {
|
||||
return "", 0
|
||||
}
|
||||
return meta.Models[0].ID, meta.Models[0].MaxTokens
|
||||
}
|
||||
|
||||
func resolveAccountModelConfig(accountID uint, provider string) (string, int, error) {
|
||||
if accountID > 0 {
|
||||
rows, err := agentAccountModelRepo.List(repo.WithByAccountID(accountID), repo.WithOrderAsc("sort_order"), repo.WithOrderAsc("id"))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
return strings.TrimSpace(rows[0].Model), rows[0].MaxTokens, nil
|
||||
}
|
||||
}
|
||||
model, maxTokens := defaultModelForProvider(provider)
|
||||
return model, maxTokens, nil
|
||||
}
|
||||
|
||||
func ResolveGeneratorConfigFromAgentSettings() (GeneratorConfig, uint, time.Duration, error) {
|
||||
status, err := loadAgentSettingValue("AIStatus")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return GeneratorConfig{}, 0, 0, err
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(status), "Enable") {
|
||||
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
||||
}
|
||||
accountValue, err := loadAgentSettingValue("AIAccountID")
|
||||
if err != nil {
|
||||
return GeneratorConfig{}, 0, 0, err
|
||||
}
|
||||
accountID, err := strconv.ParseUint(strings.TrimSpace(accountValue), 10, 64)
|
||||
if err != nil || accountID == 0 {
|
||||
return GeneratorConfig{}, 0, 0, os.ErrNotExist
|
||||
}
|
||||
config, timeout, err := ResolveGeneratorConfig(uint(accountID))
|
||||
return config, uint(accountID), timeout, err
|
||||
}
|
||||
|
||||
func LoadTerminalRuntimeSettings() (TerminalRuntimeSettings, GeneratorConfig, time.Duration, error) {
|
||||
config, accountID, timeout, err := ResolveGeneratorConfigFromAgentSettings()
|
||||
if err != nil {
|
||||
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
||||
}
|
||||
prefix, err := loadAgentSettingValue("AIPrefix")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
||||
}
|
||||
riskCommands, err := loadRiskCommands()
|
||||
if err != nil {
|
||||
return TerminalRuntimeSettings{}, GeneratorConfig{}, 0, err
|
||||
}
|
||||
return TerminalRuntimeSettings{
|
||||
AccountID: accountID,
|
||||
Prefix: strings.TrimSpace(prefix),
|
||||
RiskCommands: riskCommands,
|
||||
}, config, timeout, nil
|
||||
}
|
||||
|
||||
func loadAgentAccount(accountID uint) (*model.AgentAccount, error) {
|
||||
if accountID == 0 {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
account, err := agentAccountRepo.GetFirst(repo.WithByID(accountID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func loadAgentSettingValue(key string) (string, error) {
|
||||
var setting model.Setting
|
||||
if err := global.DB.Where("key = ?", key).First(&setting).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return "", os.ErrNotExist
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return setting.Value, nil
|
||||
}
|
||||
|
||||
func loadRiskCommands() ([]string, error) {
|
||||
value, err := loadAgentSettingValue("AIRiskCommands")
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
var commands []string
|
||||
if err := json.Unmarshal([]byte(value), &commands); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return normalizeRiskCommands(commands), nil
|
||||
}
|
||||
|
||||
func normalizeRiskCommands(commands []string) []string {
|
||||
seen := make(map[string]struct{}, len(commands))
|
||||
result := make([]string, 0, len(commands))
|
||||
for _, command := range commands {
|
||||
command = strings.TrimSpace(command)
|
||||
if command == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[command]; ok {
|
||||
continue
|
||||
}
|
||||
seen[command] = struct{}{}
|
||||
result = append(result, command)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
const validateTimeout = 15 * time.Second
|
||||
|
||||
func ValidateTerminalAccount(accountID uint) error {
|
||||
cfg, _, err := ResolveGeneratorConfig(accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generator, err := NewCommandGeneratorFromConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), validateTimeout)
|
||||
defer cancel()
|
||||
|
||||
resp, err := generator.Generate(ctx, CommandGenerateRequest{
|
||||
Input: "Print current directory.",
|
||||
Shell: "bash",
|
||||
OS: runtime.GOOS,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp == nil || resp.Command == "" {
|
||||
return fmt.Errorf("terminal ai account returned empty response")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
|
||||
)
|
||||
|
||||
const lineClearControl = 21
|
||||
|
||||
type aiInputInterceptor struct {
|
||||
config terminalai.GeneratorConfig
|
||||
timeout time.Duration
|
||||
shell string
|
||||
lang string
|
||||
prefix string
|
||||
version uint64
|
||||
|
||||
mu sync.Mutex
|
||||
currentLine string
|
||||
recentCommands []string
|
||||
riskCommands []string
|
||||
}
|
||||
|
||||
func newAIInputInterceptor(shell string, lang string) *aiInputInterceptor {
|
||||
settings, config, timeout, err := terminalai.LoadTerminalRuntimeSettings()
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
global.LOG.Warnf("load terminal ai config failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(config.APIKey) == "" && !strings.EqualFold(strings.TrimSpace(config.Provider), "ollama") {
|
||||
return nil
|
||||
}
|
||||
return &aiInputInterceptor{
|
||||
config: config,
|
||||
timeout: timeout,
|
||||
shell: strings.TrimSpace(shell),
|
||||
lang: strings.TrimSpace(lang),
|
||||
prefix: settings.Prefix,
|
||||
riskCommands: append([]string(nil), settings.RiskCommands...),
|
||||
version: terminalai.CurrentTerminalRuntimeVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) refreshSettings() error {
|
||||
if !i.needsRefresh() {
|
||||
return nil
|
||||
}
|
||||
settings, config, timeout, err := terminalai.LoadTerminalRuntimeSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
i.config = config
|
||||
i.timeout = timeout
|
||||
i.prefix = settings.Prefix
|
||||
i.riskCommands = append([]string(nil), settings.RiskCommands...)
|
||||
i.version = terminalai.CurrentTerminalRuntimeVersion()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) SetCurrentLine(line string) {
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
i.currentLine = normalizeCurrentLine(line, i.prefix)
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) needsRefresh() bool {
|
||||
if i == nil {
|
||||
return false
|
||||
}
|
||||
currentVersion := terminalai.CurrentTerminalRuntimeVersion()
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
return i.version != currentVersion
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) HandleEnter(onStart func(), onDone func(string), onError func(string)) (string, bool) {
|
||||
if i == nil {
|
||||
return "", false
|
||||
}
|
||||
if err := i.refreshSettings(); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
global.LOG.Warnf("refresh terminal ai config failed: %v", err)
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
i.mu.Lock()
|
||||
currentLine := i.currentLine
|
||||
i.currentLine = ""
|
||||
recentCommands := append([]string(nil), i.recentCommands...)
|
||||
i.mu.Unlock()
|
||||
|
||||
if !matchesAIPrefix(currentLine, i.prefix) {
|
||||
if currentLine != "" {
|
||||
i.pushRecentCommand(currentLine)
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
prompt := strings.TrimSpace(strings.TrimPrefix(currentLine, i.prefix))
|
||||
if prompt == "" {
|
||||
return "", true
|
||||
}
|
||||
if onStart != nil {
|
||||
onStart()
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), i.timeout)
|
||||
defer cancel()
|
||||
generator, err := terminalai.NewCommandGeneratorFromConfig(i.config)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("create terminal ai generator failed: %v", err)
|
||||
if onError != nil {
|
||||
onError(i18n.GetMsgWithMapAndLang(i.lang, "TerminalAIRequestFailed", map[string]interface{}{
|
||||
"err": err.Error(),
|
||||
}))
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
resp, err := generator.Generate(ctx, terminalai.CommandGenerateRequest{
|
||||
Input: prompt,
|
||||
Shell: firstNonEmpty(i.shell, filepath.Base(strings.TrimSpace(os.Getenv("SHELL")))),
|
||||
OS: runtime.GOOS,
|
||||
RecentCommands: recentCommands,
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Errorf("generate terminal ai command failed: %v", err)
|
||||
if onError != nil {
|
||||
onError(i18n.GetMsgWithMapAndLang(i.lang, "TerminalAIRequestFailed", map[string]interface{}{
|
||||
"err": err.Error(),
|
||||
}))
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
if onDone != nil {
|
||||
onDone(i18n.GetMsgWithMapAndLang(i.lang, "TerminalAIReadyToExecute", map[string]interface{}{
|
||||
"duration": formatAIDuration(time.Since(start)),
|
||||
"tokens": resolveTotalTokens(resp.Usage),
|
||||
}))
|
||||
}
|
||||
if i.isRiskCommand(resp.Command) {
|
||||
return "# " + i18n.GetMsgWithMapAndLang(i.lang, "TerminalAIBlockedRiskyCommand", map[string]interface{}{
|
||||
"command": resp.Command,
|
||||
}), true
|
||||
}
|
||||
return resp.Command, strings.TrimSpace(resp.Command) != ""
|
||||
}
|
||||
|
||||
func resolveTotalTokens(usage terminalai.ResponseUsage) int {
|
||||
if usage.TotalTokens > 0 {
|
||||
return usage.TotalTokens
|
||||
}
|
||||
return usage.PromptTokens + usage.CompletionTokens
|
||||
}
|
||||
|
||||
func formatAIDuration(duration time.Duration) string {
|
||||
if duration < time.Millisecond {
|
||||
return duration.String()
|
||||
}
|
||||
return duration.Round(time.Millisecond).String()
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) pushRecentCommand(command string) {
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
command = strings.TrimSpace(command)
|
||||
if command == "" || strings.HasPrefix(command, i.prefix) {
|
||||
return
|
||||
}
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
i.recentCommands = append([]string{command}, i.recentCommands...)
|
||||
if len(i.recentCommands) > 8 {
|
||||
i.recentCommands = i.recentCommands[:8]
|
||||
}
|
||||
}
|
||||
|
||||
func isEnterInput(data []byte) bool {
|
||||
if len(data) == 1 && (data[0] == '\r' || data[0] == '\n') {
|
||||
return true
|
||||
}
|
||||
return len(data) == 2 && data[0] == '\r' && data[1] == '\n'
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sanitizeInputLine(raw string) string {
|
||||
return strings.TrimSpace(raw)
|
||||
}
|
||||
|
||||
func matchesAIPrefix(line, prefix string) bool {
|
||||
line = strings.TrimSpace(line)
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
if line == "" || prefix == "" {
|
||||
return false
|
||||
}
|
||||
return line == prefix || strings.HasPrefix(line, prefix+" ")
|
||||
}
|
||||
|
||||
func normalizeCurrentLine(raw, prefix string) string {
|
||||
line := sanitizeInputLine(raw)
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
if line == "" || prefix == "" {
|
||||
return line
|
||||
}
|
||||
if matchesAIPrefix(line, prefix) {
|
||||
return line
|
||||
}
|
||||
prefixIdx := strings.Index(line, prefix)
|
||||
if prefixIdx < 0 {
|
||||
return line
|
||||
}
|
||||
for _, marker := range []string{"# ", "$ ", "% ", "> "} {
|
||||
if idx := strings.LastIndex(line[:prefixIdx], marker); idx >= 0 {
|
||||
promptPart := strings.TrimSpace(line[:idx+len(marker)-1])
|
||||
if looksLikePromptPrefix(promptPart) {
|
||||
return strings.TrimSpace(line[idx+len(marker):])
|
||||
}
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func looksLikePromptPrefix(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
if strings.ContainsAny(value, "'\"`") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *aiInputInterceptor) isRiskCommand(command string) bool {
|
||||
command = strings.ToLower(strings.TrimSpace(command))
|
||||
if command == "" {
|
||||
return false
|
||||
}
|
||||
for _, riskCommand := range i.riskCommands {
|
||||
riskCommand = strings.ToLower(strings.TrimSpace(riskCommand))
|
||||
if riskCommand == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(command, riskCommand) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -3,9 +3,11 @@ package terminal
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -14,20 +16,25 @@ type LocalWsSession struct {
|
||||
slave *LocalCommand
|
||||
wsConn *websocket.Conn
|
||||
|
||||
allowCtrlC bool
|
||||
writeMutex sync.Mutex
|
||||
allowCtrlC bool
|
||||
writeMutex sync.Mutex
|
||||
lang string
|
||||
aiInterceptor *aiInputInterceptor
|
||||
}
|
||||
|
||||
func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) {
|
||||
if err := slave.ResizeTerminal(cols, rows); err != nil {
|
||||
global.LOG.Errorf("ssh pty change windows size failed, err: %v", err)
|
||||
}
|
||||
lang := i18n.GetLanguageFromDB()
|
||||
|
||||
return &LocalWsSession{
|
||||
slave: slave,
|
||||
wsConn: wsConn,
|
||||
|
||||
allowCtrlC: allowCtrlC,
|
||||
allowCtrlC: allowCtrlC,
|
||||
lang: lang,
|
||||
aiInterceptor: newAIInputInterceptor("", lang),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -108,6 +115,19 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||
}
|
||||
if isEnterInput(decodeBytes) {
|
||||
if sws.aiInterceptor != nil {
|
||||
sws.aiInterceptor.SetCurrentLine(msgObj.Line)
|
||||
}
|
||||
if generated, handled := sws.aiInterceptor.HandleEnter(sws.notifyAIThinking, sws.notifyAIDone, sws.notifyAIError); handled {
|
||||
payload := []byte{lineClearControl}
|
||||
if strings.TrimSpace(generated) != "" {
|
||||
payload = append(payload, []byte(generated)...)
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(payload)
|
||||
continue
|
||||
}
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||
case WsMsgHeartbeat:
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||
@@ -119,6 +139,33 @@ func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) notifyAIThinking() {
|
||||
if sws == nil {
|
||||
return
|
||||
}
|
||||
if err := sws.masterWrite([]byte("\r\n" + i18n.GetMsgByKeyAndLang(sws.lang, "TerminalAIThinking") + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai thinking message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) notifyAIDone(message string) {
|
||||
if sws == nil || strings.TrimSpace(message) == "" {
|
||||
return
|
||||
}
|
||||
if err := sws.masterWrite([]byte(message + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai done message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) notifyAIError(message string) {
|
||||
if sws == nil || strings.TrimSpace(message) == "" {
|
||||
return
|
||||
}
|
||||
if err := sws.masterWrite([]byte(message + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai error message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
|
||||
if _, err := sws.slave.Write(cmdBytes); err != nil {
|
||||
global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err)
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/gorilla/websocket"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -43,20 +45,22 @@ const (
|
||||
type WsMsg struct {
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data,omitempty"` // WsMsgCmd
|
||||
Line string `json:"line,omitempty"` // WsMsgCmd
|
||||
Cols int `json:"cols,omitempty"` // WsMsgResize
|
||||
Rows int `json:"rows,omitempty"` // WsMsgResize
|
||||
Timestamp int `json:"timestamp,omitempty"` // WsMsgHeartbeat
|
||||
}
|
||||
|
||||
type LogicSshWsSession struct {
|
||||
stdinPipe io.WriteCloser
|
||||
comboOutput *safeBuffer
|
||||
logBuff *safeBuffer
|
||||
inputFilterBuff *safeBuffer
|
||||
session *ssh.Session
|
||||
wsConn *websocket.Conn
|
||||
isAdmin bool
|
||||
IsFlagged bool
|
||||
stdinPipe io.WriteCloser
|
||||
comboOutput *safeBuffer
|
||||
logBuff *safeBuffer
|
||||
session *ssh.Session
|
||||
wsConn *websocket.Conn
|
||||
lang string
|
||||
isAdmin bool
|
||||
IsFlagged bool
|
||||
aiInterceptor *aiInputInterceptor
|
||||
}
|
||||
|
||||
func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websocket.Conn, initCmd string) (*LogicSshWsSession, error) {
|
||||
@@ -72,7 +76,6 @@ func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websock
|
||||
|
||||
comboWriter := new(safeBuffer)
|
||||
logBuf := new(safeBuffer)
|
||||
inputBuf := new(safeBuffer)
|
||||
sshSession.Stdout = comboWriter
|
||||
sshSession.Stderr = comboWriter
|
||||
|
||||
@@ -91,15 +94,17 @@ func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websock
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _ = stdinP.Write([]byte(initCmd + "\n"))
|
||||
}
|
||||
lang := i18n.GetLanguageFromDB()
|
||||
return &LogicSshWsSession{
|
||||
stdinPipe: stdinP,
|
||||
comboOutput: comboWriter,
|
||||
logBuff: logBuf,
|
||||
inputFilterBuff: inputBuf,
|
||||
session: sshSession,
|
||||
wsConn: wsConn,
|
||||
isAdmin: true,
|
||||
IsFlagged: false,
|
||||
stdinPipe: stdinP,
|
||||
comboOutput: comboWriter,
|
||||
logBuff: logBuf,
|
||||
session: sshSession,
|
||||
wsConn: wsConn,
|
||||
lang: lang,
|
||||
isAdmin: true,
|
||||
IsFlagged: false,
|
||||
aiInterceptor: newAIInputInterceptor("", lang),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -151,6 +156,19 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||
}
|
||||
if isEnterInput(decodeBytes) {
|
||||
if sws.aiInterceptor != nil {
|
||||
sws.aiInterceptor.SetCurrentLine(msgObj.Line)
|
||||
}
|
||||
if generated, handled := sws.aiInterceptor.HandleEnter(sws.notifyAIThinking, sws.notifyAIDone, sws.notifyAIError); handled {
|
||||
payload := []byte{lineClearControl}
|
||||
if strings.TrimSpace(generated) != "" {
|
||||
payload = append(payload, []byte(generated)...)
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(payload)
|
||||
continue
|
||||
}
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||
case WsMsgHeartbeat:
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, wsData)
|
||||
@@ -162,6 +180,33 @@ func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) notifyAIThinking() {
|
||||
if sws == nil || sws.comboOutput == nil {
|
||||
return
|
||||
}
|
||||
if _, err := sws.comboOutput.Write([]byte("\r\n" + i18n.GetMsgByKeyAndLang(sws.lang, "TerminalAIThinking") + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai thinking message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) notifyAIDone(message string) {
|
||||
if sws == nil || sws.comboOutput == nil || strings.TrimSpace(message) == "" {
|
||||
return
|
||||
}
|
||||
if _, err := sws.comboOutput.Write([]byte(message + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai done message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) notifyAIError(message string) {
|
||||
if sws == nil || sws.comboOutput == nil || strings.TrimSpace(message) == "" {
|
||||
return
|
||||
}
|
||||
if _, err := sws.comboOutput.Write([]byte(message + "\r\n")); err != nil {
|
||||
global.LOG.Errorf("write terminal ai error message failed, err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
|
||||
if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
|
||||
global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err)
|
||||
|
||||
@@ -144,7 +144,7 @@ func getDownloadProcess(progress DownloadProgress) (res []byte, err error) {
|
||||
for _, k := range progress.Keys {
|
||||
value := global.CACHE.Get(k)
|
||||
if value == "" {
|
||||
return nil, fmt.Errorf("get cache error,err value is nil")
|
||||
continue
|
||||
}
|
||||
downloadProcess := &files.Process{}
|
||||
_ = json.Unmarshal([]byte(value), downloadProcess)
|
||||
|
||||
@@ -94,7 +94,7 @@ func (b *BaseApi) MFALogin(c *gin.Context) {
|
||||
|
||||
user, msgKey, err := authService.MFALogin(c, req, string(entrance))
|
||||
go saveLoginLogs(c, wrapLoginErr(msgKey, err))
|
||||
if msgKey == "ErrAuth" {
|
||||
if msgKey == "ErrAuth" || msgKey == "ErrMFA" {
|
||||
helper.BadAuth(c, msgKey, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ type ApiGroup struct {
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
var (
|
||||
hostService = service.NewIHostService()
|
||||
authService = service.NewIAuthService()
|
||||
backupService = service.NewIBackupService()
|
||||
settingService = service.NewISettingService()
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/core/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/core/app/service"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/copier"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/encrypt"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/ssh"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/terminal"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Create host
|
||||
// @Accept json
|
||||
// @Param request body dto.HostOperate true "request"
|
||||
// @Success 200 {object} dto.HostInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts [post]
|
||||
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"}
|
||||
func (b *BaseApi) CreateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
host, err := hostService.Create(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, host)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Test host conn by info
|
||||
// @Accept json
|
||||
// @Param request body dto.HostConnTest true "request"
|
||||
// @Success 200 {boolean} status
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/test/byinfo [post]
|
||||
func (b *BaseApi) TestByInfo(c *gin.Context) {
|
||||
var req dto.HostConnTest
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
connStatus := hostService.TestByInfo(req)
|
||||
helper.SuccessWithData(c, connStatus)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Test host conn by host id
|
||||
// @Accept json
|
||||
// @Param id path integer true "request"
|
||||
// @Success 200 {boolean} connStatus
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/test/byid/:id [post]
|
||||
func (b *BaseApi) TestByID(c *gin.Context) {
|
||||
idParam, ok := c.Params.Get("id")
|
||||
if !ok {
|
||||
helper.BadRequest(c, errors.New("no such params find in request"))
|
||||
return
|
||||
}
|
||||
intNum, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
connStatus := hostService.TestLocalConn(uint(intNum))
|
||||
helper.SuccessWithData(c, connStatus)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Load host tree
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchForTree true "request"
|
||||
// @Success 200 {array} dto.HostTree
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/tree [post]
|
||||
func (b *BaseApi) HostTree(c *gin.Context) {
|
||||
var req dto.SearchForTree
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := hostService.SearchForTree(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, data)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Page host
|
||||
// @Accept json
|
||||
// @Param request body dto.SearchPageWithGroup true "request"
|
||||
// @Success 200 {object} dto.PageResult
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/search [post]
|
||||
func (b *BaseApi) SearchHost(c *gin.Context) {
|
||||
var req dto.SearchPageWithGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
total, list, err := hostService.SearchWithPage(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
helper.SuccessWithData(c, dto.PageResult{
|
||||
Items: list,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Delete host
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByIDs true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/del [post]
|
||||
// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"}
|
||||
func (b *BaseApi) DeleteHost(c *gin.Context) {
|
||||
var req dto.OperateByIDs
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := hostService.Delete(req.IDs); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Update host
|
||||
// @Accept json
|
||||
// @Param request body dto.HostOperate true "request"
|
||||
// @Success 200 {object} dto.HostInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/update [post]
|
||||
// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机信息 [name][addr]","formatEN":"update host [name][addr]"}
|
||||
func (b *BaseApi) UpdateHost(c *gin.Context) {
|
||||
var req dto.HostOperate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(req.Password) != 0 && req.AuthMode == "password" {
|
||||
req.Password, err = hostService.EncryptHost(req.Password)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
req.PrivateKey = ""
|
||||
req.PassPhrase = ""
|
||||
}
|
||||
if len(req.PrivateKey) != 0 && req.AuthMode == "key" {
|
||||
req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if len(req.PassPhrase) != 0 {
|
||||
req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase)
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
req.Password = ""
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["name"] = req.Name
|
||||
upMap["group_id"] = req.GroupID
|
||||
upMap["addr"] = req.Addr
|
||||
upMap["port"] = req.Port
|
||||
upMap["user"] = req.User
|
||||
upMap["auth_mode"] = req.AuthMode
|
||||
upMap["remember_password"] = req.RememberPassword
|
||||
if req.AuthMode == "password" {
|
||||
upMap["password"] = req.Password
|
||||
upMap["private_key"] = ""
|
||||
upMap["pass_phrase"] = ""
|
||||
} else {
|
||||
upMap["password"] = ""
|
||||
upMap["private_key"] = req.PrivateKey
|
||||
upMap["pass_phrase"] = req.PassPhrase
|
||||
}
|
||||
upMap["description"] = req.Description
|
||||
hostItem, err := hostService.Update(req.ID, upMap)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, hostItem)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Update host group
|
||||
// @Accept json
|
||||
// @Param request body dto.ChangeHostGroup true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/update/group [post]
|
||||
// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"}
|
||||
func (b *BaseApi) UpdateHostGroup(c *gin.Context) {
|
||||
var req dto.ChangeHostGroup
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
upMap := make(map[string]interface{})
|
||||
upMap["group_id"] = req.GroupID
|
||||
if _, err := hostService.Update(req.ID, upMap); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
|
||||
// @Tags Host
|
||||
// @Summary Get host info
|
||||
// @Accept json
|
||||
// @Param request body dto.OperateByID true "request"
|
||||
// @Success 200 {object} dto.HostInfo
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /core/hosts/info [post]
|
||||
func (b *BaseApi) GetHostByID(c *gin.Context) {
|
||||
var req dto.OperateByID
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
info, err := hostService.GetHostByID(req.ID)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, info)
|
||||
}
|
||||
|
||||
func (b *BaseApi) WsSsh(c *gin.Context) {
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if global.CONF.Base.IsDemo {
|
||||
if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(c.Query("id"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) {
|
||||
return
|
||||
}
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) {
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) {
|
||||
return
|
||||
}
|
||||
host, err := service.GetHostInfo(uint(id))
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) {
|
||||
return
|
||||
}
|
||||
var connInfo ssh.ConnInfo
|
||||
_ = copier.Copy(&connInfo, &host)
|
||||
connInfo.PrivateKey = []byte(host.PrivateKey)
|
||||
if len(host.PassPhrase) != 0 {
|
||||
connInfo.PassPhrase = []byte(host.PassPhrase)
|
||||
}
|
||||
|
||||
client, err := ssh.NewClient(connInfo)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "")
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
defer sws.Close()
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
sws.Start(quitChan)
|
||||
go sws.Wait(quitChan)
|
||||
|
||||
<-quitChan
|
||||
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
var upGrader = websocket.Upgrader{
|
||||
ReadBufferSize: 4096,
|
||||
WriteBufferSize: 16384,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handler ws faled:, err: %v", err)
|
||||
dt := time.Now().Add(time.Second)
|
||||
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
|
||||
wsData, err := json.Marshal(terminal.WsMsg{
|
||||
Type: terminal.WsMsgCmd,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
|
||||
})
|
||||
if err != nil {
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
|
||||
} else {
|
||||
_ = ws.WriteMessage(websocket.TextMessage, wsData)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -228,3 +230,31 @@ func (b *BaseApi) RunScript(c *gin.Context) {
|
||||
dt := time.Now().Add(time.Second)
|
||||
_ = wsConn.WriteControl(websocket.CloseMessage, nil, dt)
|
||||
}
|
||||
|
||||
var upGrader = websocket.Upgrader{
|
||||
ReadBufferSize: 4096,
|
||||
WriteBufferSize: 16384,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func wshandleError(ws *websocket.Conn, err error) bool {
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handler ws faled:, err: %v", err)
|
||||
dt := time.Now().Add(time.Second)
|
||||
if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil {
|
||||
wsData, marshalErr := json.Marshal(terminal.WsMsg{
|
||||
Type: terminal.WsMsgCmd,
|
||||
Data: base64.StdEncoding.EncodeToString([]byte(err.Error())),
|
||||
})
|
||||
if marshalErr != nil {
|
||||
_ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}"))
|
||||
} else {
|
||||
_ = ws.WriteMessage(websocket.TextMessage, wsData)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/core/app/model"
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type HostRepo struct{}
|
||||
|
||||
type IHostRepo interface {
|
||||
Get(opts ...global.DBOption) (model.Host, error)
|
||||
GetList(opts ...global.DBOption) ([]model.Host, error)
|
||||
Page(limit, offset int, opts ...global.DBOption) (int64, []model.Host, error)
|
||||
Create(host *model.Host) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
UpdateGroup(group, newGroup uint) error
|
||||
Delete(opts ...global.DBOption) error
|
||||
|
||||
WithByInfo(info string) global.DBOption
|
||||
WithByPort(port uint) global.DBOption
|
||||
WithByUser(user string) global.DBOption
|
||||
}
|
||||
|
||||
func NewIHostRepo() IHostRepo {
|
||||
return &HostRepo{}
|
||||
}
|
||||
|
||||
func (h *HostRepo) Get(opts ...global.DBOption) (model.Host, error) {
|
||||
var host model.Host
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.First(&host).Error
|
||||
return host, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) GetList(opts ...global.DBOption) ([]model.Host, error) {
|
||||
var hosts []model.Host
|
||||
db := global.DB.Model(&model.Host{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&hosts).Error
|
||||
return hosts, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.Host, error) {
|
||||
var users []model.Host
|
||||
db := global.DB.Model(&model.Host{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
count := int64(0)
|
||||
db = db.Count(&count)
|
||||
err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error
|
||||
return count, users, err
|
||||
}
|
||||
|
||||
func (h *HostRepo) WithByInfo(info string) global.DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
if len(info) == 0 {
|
||||
return g
|
||||
}
|
||||
infoStr := "%" + info + "%"
|
||||
return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HostRepo) WithByPort(port uint) global.DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("port = ?", port)
|
||||
}
|
||||
}
|
||||
func (h *HostRepo) WithByUser(user string) global.DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("user = ?", user)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HostRepo) Create(host *model.Host) error {
|
||||
return global.DB.Create(host).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) UpdateGroup(group, newGroup uint) error {
|
||||
return global.DB.Model(&model.Host{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error
|
||||
}
|
||||
|
||||
func (h *HostRepo) Delete(opts ...global.DBOption) error {
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
return db.Delete(&model.Host{}).Error
|
||||
}
|
||||
@@ -11,6 +11,7 @@ type IScriptRepo interface {
|
||||
GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error)
|
||||
Create(script *model.ScriptLibrary) error
|
||||
Update(id uint, vars map[string]interface{}) error
|
||||
UpdateGroup(group, newGroup uint) error
|
||||
Page(limit, offset int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error)
|
||||
Delete(opts ...global.DBOption) error
|
||||
SyncAll(scripts []model.ScriptLibrary) error
|
||||
@@ -63,6 +64,9 @@ func (u *ScriptRepo) Create(ScriptLibrary *model.ScriptLibrary) error {
|
||||
func (u *ScriptRepo) Update(id uint, vars map[string]interface{}) error {
|
||||
return global.DB.Model(&model.ScriptLibrary{}).Where("id = ?", id).Updates(vars).Error
|
||||
}
|
||||
func (u *ScriptRepo) UpdateGroup(group, newGroup uint) error {
|
||||
return global.DB.Model(&model.ScriptLibrary{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error
|
||||
}
|
||||
|
||||
func (u *ScriptRepo) Delete(opts ...global.DBOption) error {
|
||||
db := global.DB
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import "github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
|
||||
var (
|
||||
hostRepo = repo.NewIHostRepo()
|
||||
commandRepo = repo.NewICommandRepo()
|
||||
settingRepo = repo.NewISettingRepo()
|
||||
backupRepo = repo.NewIBackupRepo()
|
||||
|
||||
@@ -100,10 +100,8 @@ func (u *GroupService) Delete(id uint) error {
|
||||
return err
|
||||
}
|
||||
switch group.Type {
|
||||
case "host":
|
||||
err = hostRepo.UpdateGroup(id, defaultGroup.ID)
|
||||
case "script":
|
||||
err = hostRepo.UpdateGroup(id, defaultGroup.ID)
|
||||
err = scriptRepo.UpdateGroup(id, defaultGroup.ID)
|
||||
case "command":
|
||||
err = commandRepo.UpdateGroup(id, defaultGroup.ID)
|
||||
case "node":
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
module github.com/1Panel-dev/1Panel/core
|
||||
|
||||
go 1.24.9
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/1panel-dev/base64Captcha v1.3.8
|
||||
|
||||
@@ -16,7 +16,7 @@ func LoadMenus() string {
|
||||
{ID: "2", Disabled: true, Title: "menu.apps", IsShow: true, Label: "App-Menu", Path: "/apps/all", Sort: 200},
|
||||
{ID: "3", Disabled: false, Title: "menu.aiTools", IsShow: true, Label: "AI-Menu", Path: "/ai/model", Sort: 300,
|
||||
Children: []dto.ShowMenu{
|
||||
{ID: "44", Disabled: false, Title: "aiTools.agents.agents", IsShow: true, Label: "Agents", Path: "/ai/agents/agent", Sort: 50},
|
||||
{ID: "44", Disabled: false, Title: "aiTools.agents.agent", IsShow: true, Label: "Agents", Path: "/ai/agents/agent", Sort: 50},
|
||||
{ID: "41", Disabled: false, Title: "aiTools.model.localModel", IsShow: true, Label: "OllamaModel", Path: "/ai/model", Sort: 100},
|
||||
{ID: "42", Disabled: false, Title: "menu.mcp", IsShow: true, Label: "MCPServer", Path: "/ai/mcp", Sort: 200},
|
||||
{ID: "43", Disabled: false, Title: "aiTools.gpu.gpu", IsShow: true, Label: "GPU", Path: "/ai/gpu", Sort: 300},
|
||||
|
||||
@@ -34,6 +34,7 @@ func Init() {
|
||||
migrations.AddDashboardCarouselSetting,
|
||||
migrations.AddEditionSetting,
|
||||
migrations.UpdateAiLocalModelMenuTitle,
|
||||
migrations.UpdateAiAgentsHideMenuTitle,
|
||||
migrations.AddDocSourceSetting,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
|
||||
@@ -30,7 +30,6 @@ var AddTable = &gormigrate.Migration{
|
||||
&model.Setting{},
|
||||
&model.BackupAccount{},
|
||||
&model.Group{},
|
||||
&model.Host{},
|
||||
&model.Command{},
|
||||
&model.UpgradeLog{},
|
||||
&model.ScriptLibrary{},
|
||||
@@ -597,7 +596,7 @@ var AddAgentsMenu = &gormigrate.Migration{
|
||||
return helper.AddMenu(dto.ShowMenu{
|
||||
ID: "44",
|
||||
Disabled: false,
|
||||
Title: "aiTools.agents.agents",
|
||||
Title: "aiTools.agents.agent",
|
||||
IsShow: true,
|
||||
Label: "Agents",
|
||||
Path: "/ai/agents/agent",
|
||||
@@ -768,7 +767,7 @@ var UpdateAiAgentsMenu = &gormigrate.Migration{
|
||||
newItem := dto.ShowMenu{
|
||||
ID: "44",
|
||||
Disabled: false,
|
||||
Title: "aiTools.agents.agents",
|
||||
Title: "aiTools.agents.agent",
|
||||
IsShow: true,
|
||||
Label: "Agents",
|
||||
Path: "/ai/agents/agent",
|
||||
@@ -866,3 +865,15 @@ var UpdateAiLocalModelMenuTitle = &gormigrate.Migration{
|
||||
).Error
|
||||
},
|
||||
}
|
||||
|
||||
var UpdateAiAgentsHideMenuTitle = &gormigrate.Migration{
|
||||
ID: "20260324-update-ai-agents-hide-menu-title",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Exec(
|
||||
`UPDATE settings
|
||||
SET value = REPLACE(value, 'aiTools.agents.agents', 'aiTools.agents.agent')
|
||||
WHERE key = 'HideMenu'
|
||||
AND value LIKE '%aiTools.agents.agents%'`,
|
||||
).Error
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ func commonGroups() []CommonRouter {
|
||||
&LogRouter{},
|
||||
&SettingRouter{},
|
||||
&CommandRouter{},
|
||||
&HostRouter{},
|
||||
&GroupRouter{},
|
||||
&ScriptRouter{},
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v2 "github.com/1Panel-dev/1Panel/core/app/api/v2"
|
||||
"github.com/1Panel-dev/1Panel/core/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type HostRouter struct{}
|
||||
|
||||
func (s *HostRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
hostRouter := Router.Group("hosts").
|
||||
Use(middleware.SessionAuth()).
|
||||
Use(middleware.PasswordExpired())
|
||||
baseApi := v2.ApiGroupApp.BaseApi
|
||||
{
|
||||
hostRouter.POST("", baseApi.CreateHost)
|
||||
hostRouter.POST("/info", baseApi.GetHostByID)
|
||||
hostRouter.POST("/del", baseApi.DeleteHost)
|
||||
hostRouter.POST("/update", baseApi.UpdateHost)
|
||||
hostRouter.POST("/update/group", baseApi.UpdateHostGroup)
|
||||
hostRouter.POST("/search", baseApi.SearchHost)
|
||||
hostRouter.POST("/tree", baseApi.HostTree)
|
||||
hostRouter.POST("/test/byinfo", baseApi.TestByInfo)
|
||||
hostRouter.POST("/test/byid/:id", baseApi.TestByID)
|
||||
|
||||
hostRouter.GET("/terminal", baseApi.WsSsh)
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ const i18nLocale = computed(() => {
|
||||
if (globalStore.language === 'ja') return ja;
|
||||
if (globalStore.language === 'ms') return ms;
|
||||
if (globalStore.language === 'ru') return ru;
|
||||
if (globalStore.language === 'pt-br') return ptBR;
|
||||
if (globalStore.language === 'pt-BR') return ptBR;
|
||||
if (globalStore.language === 'ko') return ko;
|
||||
if (globalStore.language === 'tr') return tr;
|
||||
if (globalStore.language === 'es-ES') return esES;
|
||||
|
||||
@@ -243,14 +243,8 @@ export namespace AI {
|
||||
bridgePort?: number;
|
||||
allowedOrigins?: string[];
|
||||
agentType: 'openclaw' | 'copaw';
|
||||
provider?: string;
|
||||
model?: string;
|
||||
apiType?: string;
|
||||
maxTokens?: number;
|
||||
contextWindow?: number;
|
||||
accountId?: number;
|
||||
apiKey?: string;
|
||||
baseURL?: string;
|
||||
token?: string;
|
||||
taskID: string;
|
||||
advanced: boolean;
|
||||
@@ -309,9 +303,60 @@ export namespace AI {
|
||||
model: string;
|
||||
}
|
||||
|
||||
export interface AgentOverviewReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentOverviewSnapshot {
|
||||
containerStatus: string;
|
||||
appVersion: string;
|
||||
defaultModel: string;
|
||||
channelCount: number;
|
||||
skillCount: number;
|
||||
jobCount: number;
|
||||
sessionCount: number;
|
||||
}
|
||||
|
||||
export interface AgentOverview {
|
||||
snapshot: AgentOverviewSnapshot;
|
||||
}
|
||||
|
||||
export interface AgentAccountModel {
|
||||
recordId: number;
|
||||
id: string;
|
||||
name: string;
|
||||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
reasoning: boolean;
|
||||
input: string[];
|
||||
}
|
||||
|
||||
export interface AgentAccountModelReq {
|
||||
accountId: number;
|
||||
}
|
||||
|
||||
export interface AgentAccountModelCreateReq {
|
||||
accountId: number;
|
||||
model: AgentAccountModel;
|
||||
}
|
||||
|
||||
export interface AgentAccountModelUpdateReq {
|
||||
accountId: number;
|
||||
model: AgentAccountModel;
|
||||
}
|
||||
|
||||
export interface AgentAccountModelDeleteReq {
|
||||
accountId: number;
|
||||
recordId: number;
|
||||
}
|
||||
|
||||
export interface ProviderModelInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
reasoning: boolean;
|
||||
input: string[];
|
||||
}
|
||||
|
||||
export interface ProviderInfo {
|
||||
@@ -327,10 +372,8 @@ export namespace AI {
|
||||
apiKey: string;
|
||||
rememberApiKey: boolean;
|
||||
baseURL: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
models?: AgentAccountModel[];
|
||||
remark: string;
|
||||
}
|
||||
|
||||
@@ -340,10 +383,7 @@ export namespace AI {
|
||||
apiKey: string;
|
||||
rememberApiKey: boolean;
|
||||
baseURL: string;
|
||||
model: string;
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
remark: string;
|
||||
syncAgents: boolean;
|
||||
}
|
||||
@@ -363,10 +403,8 @@ export namespace AI {
|
||||
apiKey: string;
|
||||
rememberApiKey: boolean;
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
models: AgentAccountModel[];
|
||||
apiType: string;
|
||||
maxTokens: number;
|
||||
contextWindow: number;
|
||||
verified: boolean;
|
||||
remark: string;
|
||||
createdAt: string;
|
||||
@@ -429,7 +467,7 @@ export namespace AI {
|
||||
|
||||
export interface AgentChannelPairingApproveReq {
|
||||
agentId: number;
|
||||
type: 'feishu' | 'telegram' | 'discord' | 'wecom';
|
||||
type: 'feishu' | 'telegram' | 'discord' | 'wecom' | 'dingtalk-connector';
|
||||
pairingCode: string;
|
||||
}
|
||||
|
||||
@@ -453,6 +491,37 @@ export namespace AI {
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export interface AgentDingTalkConfigReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentDingTalkConfig {
|
||||
enabled: boolean;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';
|
||||
allowFrom: string[];
|
||||
groupPolicy: 'open' | 'allowlist' | 'disabled';
|
||||
groupAllowFrom: string[];
|
||||
installed: boolean;
|
||||
}
|
||||
|
||||
export interface AgentDingTalkConfigUpdateReq {
|
||||
agentId: number;
|
||||
enabled: boolean;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';
|
||||
allowFrom: string[];
|
||||
groupPolicy: 'open' | 'allowlist' | 'disabled';
|
||||
groupAllowFrom: string[];
|
||||
}
|
||||
|
||||
export interface AgentWeixinLoginReq {
|
||||
agentId: number;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentQQBotConfigReq {
|
||||
agentId: number;
|
||||
}
|
||||
@@ -473,13 +542,13 @@ export namespace AI {
|
||||
|
||||
export interface AgentPluginInstallReq {
|
||||
agentId: number;
|
||||
type: 'qqbot' | 'wecom';
|
||||
type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
export interface AgentPluginCheckReq {
|
||||
agentId: number;
|
||||
type: 'qqbot' | 'wecom';
|
||||
type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
|
||||
}
|
||||
|
||||
export interface AgentPluginStatus {
|
||||
@@ -527,11 +596,67 @@ export namespace AI {
|
||||
export interface AgentOtherConfig {
|
||||
userTimezone: string;
|
||||
browserEnabled: boolean;
|
||||
npmRegistry: string;
|
||||
}
|
||||
|
||||
export interface AgentOtherConfigUpdateReq {
|
||||
agentId: number;
|
||||
userTimezone: string;
|
||||
browserEnabled: boolean;
|
||||
npmRegistry: string;
|
||||
}
|
||||
|
||||
export interface AgentConfigFileReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentConfigFile {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface AgentConfigFileUpdateReq {
|
||||
agentId: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface AgentSkillsReq {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
export interface AgentSkillSearchReq {
|
||||
agentId: number;
|
||||
source: 'clawhub' | 'skillhub';
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
export interface AgentSkillItem {
|
||||
name: string;
|
||||
description: string;
|
||||
source: string;
|
||||
bundled: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export interface AgentSkillSearchItem {
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
summary: string;
|
||||
version: string;
|
||||
source: string;
|
||||
score: string;
|
||||
}
|
||||
|
||||
export interface AgentSkillUpdateReq {
|
||||
agentId: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface AgentSkillInstallReq {
|
||||
agentId: number;
|
||||
source: 'clawhub' | 'skillhub';
|
||||
slug: string;
|
||||
taskID: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ export namespace Container {
|
||||
runningCount: number;
|
||||
configFile: string;
|
||||
workdir: string;
|
||||
composeFileExists: boolean;
|
||||
path: string;
|
||||
containers: Array<ComposeContainer>;
|
||||
expand: boolean;
|
||||
|
||||
@@ -82,6 +82,13 @@ export namespace Setting {
|
||||
scrollback: string;
|
||||
scrollSensitivity: string;
|
||||
}
|
||||
export interface TerminalAIInfo {
|
||||
aiStatus: string;
|
||||
aiAccountId: string;
|
||||
aiPrefix: string;
|
||||
aiRiskCommands: string;
|
||||
aiRiskCommandsDefault?: string;
|
||||
}
|
||||
export interface SettingUpdate {
|
||||
key: string;
|
||||
value: string;
|
||||
|
||||
@@ -439,6 +439,7 @@ export namespace Website {
|
||||
proxyProtocol?: string;
|
||||
sni?: boolean;
|
||||
proxySSLName: string;
|
||||
sslVerify?: boolean;
|
||||
cors: boolean;
|
||||
allowOrigins: string;
|
||||
allowMethods: string;
|
||||
|
||||
@@ -113,6 +113,10 @@ export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/model/update`, req);
|
||||
};
|
||||
|
||||
export const getAgentOverview = (req: AI.AgentOverviewReq) => {
|
||||
return http.post<AI.AgentOverview>(`/ai/agents/overview`, req);
|
||||
};
|
||||
|
||||
export const getAgentProviders = () => {
|
||||
return http.get<AI.ProviderInfo[]>(`/ai/agents/providers`);
|
||||
};
|
||||
@@ -129,6 +133,22 @@ export const pageAgentAccounts = (req: AI.AgentAccountSearch) => {
|
||||
return http.post<ResPage<AI.AgentAccountItem>>(`/ai/agents/accounts/search`, req);
|
||||
};
|
||||
|
||||
export const getAgentAccountModels = (req: AI.AgentAccountModelReq) => {
|
||||
return http.post<AI.AgentAccountModel[]>(`/ai/agents/accounts/models`, req);
|
||||
};
|
||||
|
||||
export const createAgentAccountModel = (req: AI.AgentAccountModelCreateReq) => {
|
||||
return http.post(`/ai/agents/accounts/models/create`, req);
|
||||
};
|
||||
|
||||
export const updateAgentAccountModel = (req: AI.AgentAccountModelUpdateReq) => {
|
||||
return http.post(`/ai/agents/accounts/models/update`, req);
|
||||
};
|
||||
|
||||
export const deleteAgentAccountModel = (req: AI.AgentAccountModelDeleteReq) => {
|
||||
return http.post(`/ai/agents/accounts/models/delete`, req);
|
||||
};
|
||||
|
||||
export const verifyAgentAccount = (req: AI.AgentAccountVerifyReq) => {
|
||||
return http.post(`/ai/agents/accounts/verify`, req);
|
||||
};
|
||||
@@ -169,6 +189,18 @@ export const updateAgentWecomConfig = (req: AI.AgentWecomConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/channel/wecom/update`, req);
|
||||
};
|
||||
|
||||
export const getAgentDingTalkConfig = (req: AI.AgentDingTalkConfigReq) => {
|
||||
return http.post<AI.AgentDingTalkConfig>(`/ai/agents/channel/dingtalk/get`, req);
|
||||
};
|
||||
|
||||
export const updateAgentDingTalkConfig = (req: AI.AgentDingTalkConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/channel/dingtalk/update`, req);
|
||||
};
|
||||
|
||||
export const loginAgentWeixinChannel = (req: AI.AgentWeixinLoginReq) => {
|
||||
return http.post(`/ai/agents/channel/weixin/login`, req);
|
||||
};
|
||||
|
||||
export const getAgentQQBotConfig = (req: AI.AgentQQBotConfigReq) => {
|
||||
return http.post<AI.AgentQQBotConfig>(`/ai/agents/channel/qqbot/get`, req);
|
||||
};
|
||||
@@ -201,6 +233,30 @@ export const updateAgentOtherConfig = (req: AI.AgentOtherConfigUpdateReq) => {
|
||||
return http.post(`/ai/agents/other/update`, req);
|
||||
};
|
||||
|
||||
export const getAgentConfigFile = (req: AI.AgentConfigFileReq) => {
|
||||
return http.post<AI.AgentConfigFile>(`/ai/agents/config-file/get`, req);
|
||||
};
|
||||
|
||||
export const updateAgentConfigFile = (req: AI.AgentConfigFileUpdateReq) => {
|
||||
return http.post(`/ai/agents/config-file/update`, req);
|
||||
};
|
||||
|
||||
export const listAgentSkills = (req: AI.AgentSkillsReq) => {
|
||||
return http.post<AI.AgentSkillItem[]>(`/ai/agents/skills/list`, req);
|
||||
};
|
||||
|
||||
export const searchAgentSkills = (req: AI.AgentSkillSearchReq) => {
|
||||
return http.post<AI.AgentSkillSearchItem[]>(`/ai/agents/skills/search`, req);
|
||||
};
|
||||
|
||||
export const updateAgentSkill = (req: AI.AgentSkillUpdateReq) => {
|
||||
return http.post(`/ai/agents/skills/update`, req);
|
||||
};
|
||||
|
||||
export const installAgentSkill = (req: AI.AgentSkillInstallReq) => {
|
||||
return http.post(`/ai/agents/skills/install`, req);
|
||||
};
|
||||
|
||||
export const approveAgentChannelPairing = (req: AI.AgentChannelPairingApproveReq) => {
|
||||
return http.post(`/ai/agents/channel/pairing/approve`, req);
|
||||
};
|
||||
|
||||
@@ -98,6 +98,10 @@ export const wgetFile = (params: File.FileWget) => {
|
||||
return http.post<File.FileWgetRes>('files/wget', params);
|
||||
};
|
||||
|
||||
export const stopWgetFile = (key: string) => {
|
||||
return http.post('files/wget/stop', { key });
|
||||
};
|
||||
|
||||
export const moveFile = (params: File.FileMove) => {
|
||||
return http.post<File.File>('files/move', params, TimeoutEnum.T_5M);
|
||||
};
|
||||
|
||||
@@ -73,6 +73,12 @@ export const updateAgentSetting = (param: Setting.SettingUpdate) => {
|
||||
export const getAgentSettingInfo = () => {
|
||||
return http.post<Setting.SettingInfo>(`/settings/search`);
|
||||
};
|
||||
export const getAgentTerminalAIInfo = () => {
|
||||
return http.post<Setting.TerminalAIInfo>(`/settings/terminal/ai/search`);
|
||||
};
|
||||
export const updateAgentTerminalAIInfo = (param: Setting.TerminalAIInfo) => {
|
||||
return http.post(`/settings/terminal/ai/update`, param);
|
||||
};
|
||||
export const getAgentSettingByKey = (key: string) => {
|
||||
return http.get<string>(`/settings/get/${key}`);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,13 @@ import { Base64 } from 'js-base64';
|
||||
import { deepCopy } from '@/utils/util';
|
||||
|
||||
export const searchHosts = (params: Host.SearchWithPage) => {
|
||||
return http.post<ResPage<Host.Host>>(`/core/hosts/search`, params);
|
||||
return http.postLocalNode<ResPage<Host.Host>>(`/hosts/search`, params);
|
||||
};
|
||||
export const getHostByID = (id: number) => {
|
||||
return http.post<Host.Host>(`/core/hosts/info`, { id: id });
|
||||
return http.postLocalNode<Host.Host>(`/hosts/info`, { id: id });
|
||||
};
|
||||
export const getHostTree = (params: Host.ReqSearch) => {
|
||||
return http.post<Array<Host.HostTree>>(`/core/hosts/tree`, params);
|
||||
return http.postLocalNode<Array<Host.HostTree>>(`/hosts/tree`, params);
|
||||
};
|
||||
export const updateLocalConn = (param: { withReset: boolean; defaultConn: string }) => {
|
||||
return http.post(`/settings/ssh/default`, param);
|
||||
@@ -27,7 +27,7 @@ export const addHost = (params: Host.HostOperate) => {
|
||||
if (params.isLocal) {
|
||||
return http.post(`/settings/ssh`, request);
|
||||
}
|
||||
return http.post<Host.HostOperate>(`/core/hosts`, request);
|
||||
return http.postLocalNode<Host.HostOperate>(`/hosts`, request);
|
||||
};
|
||||
export const testByInfo = (params: Host.HostConnTest) => {
|
||||
let request = deepCopy(params) as Host.HostOperate;
|
||||
@@ -40,10 +40,10 @@ export const testByInfo = (params: Host.HostConnTest) => {
|
||||
if (params.isLocal) {
|
||||
return http.post<boolean>(`/settings/ssh/check/info`, request);
|
||||
}
|
||||
return http.post<boolean>(`/core/hosts/test/byinfo`, request);
|
||||
return http.postLocalNode<boolean>(`/hosts/test/byinfo`, request);
|
||||
};
|
||||
export const testByID = (id: number) => {
|
||||
return http.post<boolean>(`/core/hosts/test/byid/${id}`);
|
||||
return http.postLocalNode<boolean>(`/hosts/test/byid`, { id: id });
|
||||
};
|
||||
export const editHost = (params: Host.HostOperate) => {
|
||||
let request = deepCopy(params) as Host.HostOperate;
|
||||
@@ -53,13 +53,13 @@ export const editHost = (params: Host.HostOperate) => {
|
||||
if (request.privateKey) {
|
||||
request.privateKey = Base64.encode(request.privateKey);
|
||||
}
|
||||
return http.post(`/core/hosts/update`, request);
|
||||
return http.postLocalNode(`/hosts/update`, request);
|
||||
};
|
||||
export const editHostGroup = (params: Host.GroupChange) => {
|
||||
return http.post(`/core/hosts/update/group`, params);
|
||||
return http.postLocalNode(`/hosts/update/group`, params);
|
||||
};
|
||||
export const deleteHost = (params: { ids: number[] }) => {
|
||||
return http.post(`/core/hosts/del`, params);
|
||||
return http.postLocalNode(`/hosts/del`, params);
|
||||
};
|
||||
|
||||
// agent
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4776196 */
|
||||
src: url('iconfont.woff2?t=1760516880031') format('woff2'),
|
||||
url('iconfont.woff?t=1760516880031') format('woff'),
|
||||
url('iconfont.ttf?t=1760516880031') format('truetype'),
|
||||
url('iconfont.svg?t=1760516880031#iconfont') format('svg');
|
||||
src: url('iconfont.woff2?t=1773998579784') format('woff2'),
|
||||
url('iconfont.woff?t=1773998579784') format('woff'),
|
||||
url('iconfont.ttf?t=1773998579784') format('truetype'),
|
||||
url('iconfont.svg?t=1773998579784#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -14,6 +14,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.p-gailan1:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.p-tuichudenglu3:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.p-renwuzhongxin1:before {
|
||||
content: "\e807";
|
||||
}
|
||||
|
||||
.p-zhuji:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.p-pcm:before {
|
||||
content: "\e7b2";
|
||||
}
|
||||
|
||||
.p-run-menu:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,41 @@
|
||||
"css_prefix_text": "p-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "9783460",
|
||||
"name": "概览",
|
||||
"font_class": "gailan1",
|
||||
"unicode": "e62e",
|
||||
"unicode_decimal": 58926
|
||||
},
|
||||
{
|
||||
"icon_id": "26213819",
|
||||
"name": "退出登陆",
|
||||
"font_class": "tuichudenglu3",
|
||||
"unicode": "e62d",
|
||||
"unicode_decimal": 58925
|
||||
},
|
||||
{
|
||||
"icon_id": "32303204",
|
||||
"name": "任务中心",
|
||||
"font_class": "renwuzhongxin1",
|
||||
"unicode": "e807",
|
||||
"unicode_decimal": 59399
|
||||
},
|
||||
{
|
||||
"icon_id": "3453887",
|
||||
"name": "主机",
|
||||
"font_class": "zhuji",
|
||||
"unicode": "e629",
|
||||
"unicode_decimal": 58921
|
||||
},
|
||||
{
|
||||
"icon_id": "39180612",
|
||||
"name": "pcm",
|
||||
"font_class": "pcm",
|
||||
"unicode": "e7b2",
|
||||
"unicode_decimal": 59314
|
||||
},
|
||||
{
|
||||
"icon_id": "5041257",
|
||||
"name": "运行环境",
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="gailan1" unicode="" d="M485.9 408.4H315.6c-93.9 0-170.3 76.4-170.3 170.3S221.7 749 315.6 749s170.3-76.4 170.3-170.3v-170.3zM315.6 705c-69.7 0-126.3-56.7-126.3-126.3S246 452.4 315.6 452.4h126.3V578.7c0 69.7-56.6 126.3-126.3 126.3z m390.8-296.6H536.1V578.7c0 93.9 76.4 170.3 170.3 170.3s170.3-76.4 170.3-170.3-76.4-170.3-170.3-170.3z m-126.3 44h126.3c69.7 0 126.3 56.7 126.3 126.3S776 705 706.4 705s-126.3-56.7-126.3-126.3v-126.3zM706.4 19c-93.9 0-170.3 76.4-170.3 170.3V359.6h170.3c93.9 0 170.3-76.4 170.3-170.3S800.3 19 706.4 19zM580.1 315.6v-126.3c0-69.7 56.7-126.3 126.3-126.3s126.3 56.7 126.3 126.3c0 69.7-56.7 126.3-126.3 126.3H580.1zM315.6 19c-93.9 0-170.3 76.4-170.3 170.3s76.4 170.3 170.3 170.3h170.3v-170.3c0-93.9-76.4-170.3-170.3-170.3z m0 296.6c-69.7 0-126.3-56.7-126.3-126.3 0-69.7 56.7-126.3 126.3-126.3s126.3 56.7 126.3 126.3V315.6H315.6z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="tuichudenglu3" unicode="" d="M0 896h1024v-1024H0zM603.204267 265.284267h-241.834667a19.012267 19.012267 0 0 0-21.026133 14.779733 31.095467 31.095467 0 0 0-0.375467 5.870933v208.213334a18.261333 18.261333 0 0 0 20.48 20.206933h242.653867V705.979733a18.056533 18.056533 0 0 0 10.922666 17.92 17.7152 17.7152 0 0 0 20.855467-4.676266c3.413333-3.413333 7.168-6.826667 10.6496-10.478934l132.676267-132.4032q85.333333-84.957867 170.3936-170.052266a18.056533 18.056533 0 0 0 0.170666-28.637867q-121.890133-121.7536-243.848533-243.4048-34.7136-34.577067-69.495467-69.051733a18.2272 18.2272 0 0 0-20.650666-5.495467 17.954133 17.954133 0 0 0-11.5712 19.012267zM457.216 721.783467v-70.144H272.384A63.658667 63.658667 0 0 1 206.916267 586.069333c0-130.6624 0.273067-261.290667-0.170667-391.953066a65.365333 65.365333 0 0 1 50.4832-64 89.736533 89.736533 0 0 1 16.418133-1.467734h183.466667v-69.768533a13.346133 13.346133 0 0 0-2.7648-0.546133c-62.600533 0-125.3376-0.648533-187.938133 0.273066a134.2464 134.2464 0 0 0-127.146667 107.52 135.953067 135.953067 0 0 0-2.491733 25.9072c-0.170667 132.027733-0.443733 264.157867 0 396.1856a130.8672 130.8672 0 0 0 75.4688 120.456534 114.449067 114.449067 0 0 0 54.613333 13.2096c62.1568 0.170667 124.2112 0 186.368 0 1.2288-0.034133 2.321067-0.1024 3.9936-0.1024z m0 0" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="renwuzhongxin1" unicode="" d="M293.63 292.47l368.709 0.553 0.072-48-368.71-0.553zM293.903 479.47l246.768 0.37 0.072-48-246.768-0.37zM833.477 530.304l-0.006 0.007-222.66 222.66-7.03 7.029H271.5c-48.523 0-88-39.477-88-88v-576c0-48.523 39.477-88 88-88h481c48.523 0 88 39.477 88 88V523.281l-7.023 7.023z m-74.918 7.036H617.84V678.059l70.359-70.359 70.36-70.36zM752.5 56h-481c-22.056 0-40 17.944-40 40V672c0 22.056 17.944 40 40 40h298.34v-198.66c0-13.255 10.745-24 24-24H792.5V96c0-22.056-17.944-40-40-40z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="zhuji" unicode="" d="M747.52 844.8H327.68c-40.96 0-71.68-30.72-71.68-71.68v-783.36c0-35.84 30.72-66.56 71.68-66.56h424.96c40.96 0 71.68 30.72 71.68 71.68V773.12c-5.12 40.96-35.84 71.68-76.8 71.68zM768-5.12c0-10.24-10.24-20.48-20.48-20.48H327.68c-10.24 0-20.48 10.24-20.48 20.48V773.12c0 10.24 10.24 20.48 20.48 20.48h424.96c5.12 0 15.36-10.24 15.36-20.48v-778.24zM691.2 691.2H384c-15.36 0-25.6-10.24-25.6-25.6S368.64 640 384 640h307.2c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z m0-102.4H384c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h307.2c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6zM537.6 230.4c-40.96 0-76.8-35.84-76.8-76.8s35.84-76.8 76.8-76.8 76.8 35.84 76.8 76.8-35.84 76.8-76.8 76.8z m0-102.4c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6 25.6-10.24 25.6-25.6-10.24-25.6-25.6-25.6z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="pcm" unicode="" d="M165.327238 229.327238V666.672762H773.36381V554.666667h58.660571V693.345524c0 17.65181-14.336 31.98781-32.012191 31.987809H138.703238c-17.67619 0-32.01219-14.336-32.01219-32.01219v-490.642286c0-17.67619 14.336-32.01219 32.01219-32.01219h394.654476v58.660571H165.327238zM298.666667 490.666667h234.666666v-58.660572H298.666667v58.660572zM533.333333 74.654476v37.351619H298.666667v-58.684952h242.834285a31.890286 31.890286 0 0 0-8.167619 21.357714zM576 479.98781c0 17.67619 14.336 32.01219 32.01219 32.01219h277.333334c17.65181 0 31.98781-14.336 31.987809-32.01219v-405.333334c0-17.65181-14.336-31.98781-32.01219-31.987809H608.01219a31.98781 31.98781 0 0 0-32.01219 32.01219V479.963429z m58.660571-378.636191h224.012191V453.315048H634.63619v-352.012191z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="run-menu" unicode="" d="M888.875 740.25h-750c-20.625 0-37.5-16.875-37.5-37.5v-521.25c0-20.625 16.875-37.5 37.5-37.5h750c20.625 0 37.5 16.875 37.5 37.5V702.75c0 20.625-16.875 37.5-37.5 37.5z m-37.5-521.25h-675V665.25h675v-446.25zM740.75 65.25h-450c-20.625 0-37.5-16.875-37.5-37.5s16.875-37.5 37.5-37.5h450c20.625 0 37.5 16.875 37.5 37.5s-16.875 37.5-37.5 37.5zM384.5 273.375c7.5-3.75 15-5.625 22.5-5.625 7.5 0 16.875 1.875 24.375 5.625l238.125 138.75c15 9.375 22.5 24.375 22.5 41.25 0 16.875-9.375 31.875-22.5 41.25l-238.125 138.75c-15 9.375-31.875 9.375-46.875 0S362 609 362 592.125v-275.625c-1.875-18.75 7.5-33.75 22.5-43.125z m41.25 76.875v206.25l178.125-103.125-178.125-103.125z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="ssh-menu" unicode="" d="M830.068053 740.898133c-172.305067 0-315.665067 117.80096-317.412693 118.94784L494.086827 875.52l-18.568534-15.674027s-144.452267-118.94784-316.757333-118.94784H129.706667v-489.71776c1.14688-7.591253 24.958293-182.790827 355.095893-300.591786l9.885013-3.495254 9.8304 3.495254c329.64608 117.200213 354.003627 293.000533 355.150507 303.486293V740.898133h-29.600427zM494.086827 8.587947c-280.221013 103.273813-305.780053 246.033067-306.3808 246.033066V684.045653c144.50688 9.284267 262.30784 84.70528 306.3808 117.200214 44.127573-32.494933 162.52928-107.915947 306.981546-117.200214v-425.929386s-23.7568-145.65376-306.926933-249.52832zM624.667307 533.74976a130.74432 130.74432 0 0 1-130.525867 130.525867 130.74432 130.74432 0 0 1-130.58048-130.525867c0-62.09536 43.526827-113.759573 101.5808-127.085227v-244.886186h57.99936V270.9504h91.641173V328.840533h-91.641173V406.664533a130.635093 130.635093 0 0 1 101.5808 127.085227z m-203.106987 0a72.58112 72.58112 0 1 0 145.16224-0.054613 72.58112 72.58112 0 0 0-145.16224 0z" horiz-adv-x="1024" />
|
||||
|
||||
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 234 KiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user