Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions agent/app/api/v2/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ func (b *BaseApi) DeleteAgent(c *gin.Context) {
helper.Success(c)
}

// @Tags AI
// @Summary Reset Agent token
// @Accept json
// @Param request body dto.AgentTokenResetReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/token/reset [post]
func (b *BaseApi) ResetAgentToken(c *gin.Context) {
var req dto.AgentTokenResetReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.ResetToken(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}

// @Tags AI
// @Summary Update Agent model config
// @Accept json
Expand Down
4 changes: 4 additions & 0 deletions agent/app/dto/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ type AgentDeleteReq struct {
ForceDelete bool `json:"forceDelete"`
}

type AgentTokenResetReq struct {
ID uint `json:"id" validate:"required"`
}

type AgentModelConfigUpdateReq struct {
AgentID uint `json:"agentId" validate:"required"`
AccountID uint `json:"accountId" validate:"required"`
Expand Down
45 changes: 43 additions & 2 deletions agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type IAgentService interface {
Create(req dto.AgentCreateReq) (*dto.AgentItem, error)
Page(req dto.SearchWithPage) (int64, []dto.AgentItem, error)
Delete(req dto.AgentDeleteReq) error
ResetToken(req dto.AgentTokenResetReq) error
UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error
GetProviders() ([]dto.ProviderInfo, error)
CreateAccount(req dto.AgentAccountCreateReq) error
Expand Down Expand Up @@ -103,7 +104,7 @@ func (a AgentService) Create(req dto.AgentCreateReq) (*dto.AgentItem, error) {

token := strings.TrimSpace(req.Token)
if token == "" {
token = randomToken()
token = generateToken()
}
params := map[string]interface{}{
"PROVIDER": provider,
Expand Down Expand Up @@ -211,6 +212,46 @@ func (a AgentService) Delete(req dto.AgentDeleteReq) error {
return nil
}

func (a AgentService) ResetToken(req dto.AgentTokenResetReq) error {
agent, err := agentRepo.GetFirst(repo.WithByID(req.ID))
if err != nil {
return err
}
configPath := strings.TrimSpace(agent.ConfigPath)
if configPath == "" && agent.AppInstallID > 0 {
install, err := appInstallRepo.GetFirst(repo.WithByID(agent.AppInstallID))
if err != nil {
return err
}
configPath = path.Join(install.GetPath(), "data", "conf", "openclaw.json")
}
if configPath == "" {
return buserr.New("ErrRecordNotFound")
}
conf, err := readOpenclawConfig(configPath)
if err != nil {
return err
}
newToken := generateToken()
if newToken == "" {
return fmt.Errorf("generate token failed")
}
gatewayMap := ensureChildMap(conf, "gateway")
authMap := ensureChildMap(gatewayMap, "auth")
if _, ok := authMap["mode"]; !ok {
authMap["mode"] = "token"
}
authMap["token"] = newToken
if err := writeOpenclawConfigRaw(configPath, conf); err != nil {
return err
}
agent.Token = newToken
if agent.ConfigPath == "" {
agent.ConfigPath = configPath
}
return agentRepo.Save(agent)
}

func (a AgentService) UpdateModelConfig(req dto.AgentModelConfigUpdateReq) error {
agent, err := agentRepo.GetFirst(repo.WithByID(req.AgentID))
if err != nil {
Expand Down Expand Up @@ -1226,7 +1267,7 @@ func toInt(value interface{}) int {
}
}

func randomToken() string {
func generateToken() string {
bytes := make([]byte, 24)
if _, err := rand.Read(bytes); err != nil {
return ""
Expand Down
55 changes: 28 additions & 27 deletions agent/init/migration/migrations/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
"github.com/1Panel-dev/1Panel/agent/app/model"
providercatalog "github.com/1Panel-dev/1Panel/agent/app/provider"
"github.com/1Panel-dev/1Panel/agent/app/service"
"github.com/1Panel-dev/1Panel/agent/constant"
"github.com/1Panel-dev/1Panel/agent/global"
migrationutils "github.com/1Panel-dev/1Panel/agent/init/migration/migrations/utils"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/copier"
"github.com/1Panel-dev/1Panel/agent/utils/encrypt"
Expand Down Expand Up @@ -864,17 +864,38 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
if strings.TrimSpace(install.Env) != "" {
_ = json.Unmarshal([]byte(install.Env), &envMap)
}
provider := strings.ToLower(getEnvStr(envMap, "PROVIDER"))
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
}
modelName := getEnvStr(envMap, "MODEL")
baseURL := getEnvStr(envMap, "BASE_URL")
apiKey := getEnvStr(envMap, "API_KEY")
token := getEnvStr(envMap, "OPENCLAW_GATEWAY_TOKEN")
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 := defaultBaseURL(provider); ok {
if defaultURL, ok := migrationutils.DefaultBaseURL(provider); ok {
baseURL = defaultURL
}
}
Expand All @@ -900,7 +921,6 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
return err
}
}
configPath := path.Join(install.GetPath(), "data", "conf", "openclaw.json")
agent := model.Agent{
Name: install.Name,
Provider: provider,
Expand All @@ -921,22 +941,3 @@ var MigrateOpenclawAgents = &gormigrate.Migration{
return nil
},
}

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 defaultBaseURL(provider string) (string, bool) {
return providercatalog.DefaultBaseURL(provider)
}
164 changes: 164 additions & 0 deletions agent/init/migration/migrations/utils/openclaw_agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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")
}
1 change: 1 addition & 0 deletions agent/router/ro_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents", baseApi.CreateAgent)
aiToolsRouter.POST("/agents/search", baseApi.PageAgents)
aiToolsRouter.POST("/agents/delete", baseApi.DeleteAgent)
aiToolsRouter.POST("/agents/token/reset", baseApi.ResetAgentToken)
aiToolsRouter.POST("/agents/model/update", baseApi.UpdateAgentModelConfig)
aiToolsRouter.GET("/agents/providers", baseApi.GetAgentProviders)
aiToolsRouter.POST("/agents/accounts", baseApi.CreateAgentAccount)
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/interface/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ export namespace AI {
forceDelete: boolean;
}

export interface AgentTokenResetReq {
id: number;
}

export interface AgentModelConfigUpdateReq {
agentId: number;
accountId: number;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/modules/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export const deleteAgent = (req: AI.AgentDeleteReq) => {
return http.post(`/ai/agents/delete`, req);
};

export const resetAgentToken = (req: AI.AgentTokenResetReq) => {
return http.post(`/ai/agents/token/reset`, req);
};

export const updateAgentModelConfig = (req: AI.AgentModelConfigUpdateReq) => {
return http.post(`/ai/agents/model/update`, req);
};
Expand Down
Loading
Loading