1760 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			1760 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
|  * Copyright 2025 coze-dev Authors
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package plugin
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/bytedance/sonic"
 | |
| 	"github.com/getkin/kin-openapi/openapi3"
 | |
| 	gonanoid "github.com/matoous/go-nanoid"
 | |
| 	"gopkg.in/yaml.v3"
 | |
| 
 | |
| 	model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin"
 | |
| 	searchModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"
 | |
| 	productCommon "github.com/coze-dev/coze-studio/backend/api/model/flow/marketplace/product_common"
 | |
| 	productAPI "github.com/coze-dev/coze-studio/backend/api/model/flow/marketplace/product_public_api"
 | |
| 	botOpenAPI "github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/bot_open_api"
 | |
| 	pluginAPI "github.com/coze-dev/coze-studio/backend/api/model/ocean/cloud/plugin_develop"
 | |
| 	common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop_common"
 | |
| 	resCommon "github.com/coze-dev/coze-studio/backend/api/model/resource/common"
 | |
| 	"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
 | |
| 	"github.com/coze-dev/coze-studio/backend/application/base/pluginutil"
 | |
| 	"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crosssearch"
 | |
| 	pluginConf "github.com/coze-dev/coze-studio/backend/domain/plugin/conf"
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/plugin/repository"
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/plugin/service"
 | |
| 	"github.com/coze-dev/coze-studio/backend/domain/plugin/utils"
 | |
| 	searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
 | |
| 	search "github.com/coze-dev/coze-studio/backend/domain/search/service"
 | |
| 	user "github.com/coze-dev/coze-studio/backend/domain/user/service"
 | |
| 	"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/errorx"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
 | |
| 	"github.com/coze-dev/coze-studio/backend/pkg/logs"
 | |
| 	commonConsts "github.com/coze-dev/coze-studio/backend/types/consts"
 | |
| 	"github.com/coze-dev/coze-studio/backend/types/errno"
 | |
| )
 | |
| 
 | |
| var PluginApplicationSVC = &PluginApplicationService{}
 | |
| 
 | |
| type PluginApplicationService struct {
 | |
| 	DomainSVC service.PluginService
 | |
| 	eventbus  search.ResourceEventBus
 | |
| 	oss       storage.Storage
 | |
| 	userSVC   user.User
 | |
| 
 | |
| 	toolRepo   repository.ToolRepository
 | |
| 	pluginRepo repository.PluginRepository
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetOAuthSchema(ctx context.Context, req *pluginAPI.GetOAuthSchemaRequest) (resp *pluginAPI.GetOAuthSchemaResponse, err error) {
 | |
| 	return &pluginAPI.GetOAuthSchemaResponse{
 | |
| 		OauthSchema: pluginConf.GetOAuthSchema(),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetPlaygroundPluginList(ctx context.Context, req *pluginAPI.GetPlaygroundPluginListRequest) (resp *pluginAPI.GetPlaygroundPluginListResponse, err error) {
 | |
| 	var (
 | |
| 		plugins []*entity.PluginInfo
 | |
| 		total   int64
 | |
| 	)
 | |
| 	if len(req.PluginIds) > 0 {
 | |
| 		plugins, total, err = p.getPlaygroundPluginListByIDs(ctx, req.PluginIds)
 | |
| 	} else {
 | |
| 		plugins, total, err = p.getPlaygroundPluginList(ctx, req)
 | |
| 	}
 | |
| 
 | |
| 	pluginList := make([]*common.PluginInfoForPlayground, 0, len(plugins))
 | |
| 	for _, pl := range plugins {
 | |
| 		tools, err := p.toolRepo.GetPluginAllOnlineTools(ctx, pl.ID)
 | |
| 		if err != nil {
 | |
| 			return nil, errorx.Wrapf(err, "GetPluginAllOnlineTools failed, pluginID=%d", pl.ID)
 | |
| 		}
 | |
| 
 | |
| 		pluginInfo, err := p.toPluginInfoForPlayground(ctx, pl, tools)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		pluginList = append(pluginList, pluginInfo)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetPlaygroundPluginListResponse{
 | |
| 		Data: &common.GetPlaygroundPluginListData{
 | |
| 			Total:      int32(total),
 | |
| 			PluginList: pluginList,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getPlaygroundPluginListByIDs(ctx context.Context, pluginIDs []string) (plugins []*entity.PluginInfo, total int64, err error) {
 | |
| 	ids := make([]int64, 0, len(pluginIDs))
 | |
| 	for _, id := range pluginIDs {
 | |
| 		pluginID, err := strconv.ParseInt(id, 10, 64)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, fmt.Errorf("invalid pluginID '%s'", id)
 | |
| 		}
 | |
| 		ids = append(ids, pluginID)
 | |
| 	}
 | |
| 
 | |
| 	plugins, err = p.pluginRepo.MGetOnlinePlugins(ctx, ids)
 | |
| 	if err != nil {
 | |
| 		return nil, 0, errorx.Wrapf(err, "MGetOnlinePlugins failed, pluginIDs=%v", pluginIDs)
 | |
| 	}
 | |
| 
 | |
| 	total = int64(len(plugins))
 | |
| 
 | |
| 	return plugins, total, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getPlaygroundPluginList(ctx context.Context, req *pluginAPI.GetPlaygroundPluginListRequest) (plugins []*entity.PluginInfo, total int64, err error) {
 | |
| 	pageInfo := entity.PageInfo{
 | |
| 		Name: req.Name,
 | |
| 		Page: int(req.GetPage()),
 | |
| 		Size: int(req.GetSize()),
 | |
| 		SortBy: func() *entity.SortField {
 | |
| 			if req.GetOrderBy() == 0 {
 | |
| 				return ptr.Of(entity.SortByUpdatedAt)
 | |
| 			}
 | |
| 			return ptr.Of(entity.SortByCreatedAt)
 | |
| 		}(),
 | |
| 		OrderByACS: ptr.Of(false),
 | |
| 	}
 | |
| 	plugins, total, err = p.DomainSVC.ListCustomOnlinePlugins(ctx, req.GetSpaceID(), pageInfo)
 | |
| 	if err != nil {
 | |
| 		return nil, 0, errorx.Wrapf(err, "ListCustomOnlinePlugins failed, spaceID=%d", req.GetSpaceID())
 | |
| 	}
 | |
| 
 | |
| 	return plugins, total, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) toPluginInfoForPlayground(ctx context.Context, pl *entity.PluginInfo, tools []*entity.ToolInfo) (*common.PluginInfoForPlayground, error) {
 | |
| 	pluginAPIs := make([]*common.PluginApi, 0, len(tools))
 | |
| 	for _, tl := range tools {
 | |
| 		params, err := tl.ToPluginParameters()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		pluginAPIs = append(pluginAPIs, &common.PluginApi{
 | |
| 			APIID:      strconv.FormatInt(tl.ID, 10),
 | |
| 			Name:       tl.GetName(),
 | |
| 			Desc:       tl.GetDesc(),
 | |
| 			PluginID:   strconv.FormatInt(pl.ID, 10),
 | |
| 			PluginName: pl.GetName(),
 | |
| 			RunMode:    common.RunMode_Sync,
 | |
| 			Parameters: params,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	var creator *common.Creator
 | |
| 	userInfo, err := p.userSVC.GetUserInfo(context.Background(), pl.DeveloperID)
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "get user info failed, err=%v", err)
 | |
| 		creator = common.NewCreator()
 | |
| 	} else {
 | |
| 		creator = &common.Creator{
 | |
| 			ID:             strconv.FormatInt(pl.DeveloperID, 10),
 | |
| 			Name:           userInfo.Name,
 | |
| 			AvatarURL:      userInfo.IconURL,
 | |
| 			UserUniqueName: userInfo.UniqueName,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	iconURL, err := p.oss.GetObjectUrl(ctx, pl.GetIconURI())
 | |
| 	if err != nil {
 | |
| 		logs.Errorf("get plugin icon url failed, err=%v", err)
 | |
| 	}
 | |
| 
 | |
| 	authType, ok := model.ToThriftAuthType(pl.GetAuthInfo().Type)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("invalid auth type '%s'", pl.GetAuthInfo().Type)
 | |
| 	}
 | |
| 
 | |
| 	pluginInfo := &common.PluginInfoForPlayground{
 | |
| 		Auth:           int32(authType),
 | |
| 		CreateTime:     strconv.FormatInt(pl.CreatedAt/1000, 10),
 | |
| 		CreationMethod: common.CreationMethod_COZE,
 | |
| 		Creator:        creator,
 | |
| 		DescForHuman:   pl.GetDesc(),
 | |
| 		ID:             strconv.FormatInt(pl.ID, 10),
 | |
| 		IsOfficial:     pl.IsOfficial(),
 | |
| 		MaterialID:     strconv.FormatInt(pl.ID, 10),
 | |
| 		Name:           pl.GetName(),
 | |
| 		PluginIcon:     iconURL,
 | |
| 		PluginType:     pl.PluginType,
 | |
| 		SpaceID:        strconv.FormatInt(pl.SpaceID, 10),
 | |
| 		StatisticData:  common.NewPluginStatisticData(),
 | |
| 		Status:         common.PluginStatus_SUBMITTED,
 | |
| 		UpdateTime:     strconv.FormatInt(pl.UpdatedAt/1000, 10),
 | |
| 		ProjectID:      strconv.FormatInt(pl.GetAPPID(), 10),
 | |
| 		VersionName:    pl.GetVersion(),
 | |
| 		VersionTs:      pl.GetVersion(), // 兼容前端逻辑,理论上应该使用 VersionName
 | |
| 		PluginApis:     pluginAPIs,
 | |
| 	}
 | |
| 
 | |
| 	return pluginInfo, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) RegisterPluginMeta(ctx context.Context, req *pluginAPI.RegisterPluginMetaRequest) (resp *pluginAPI.RegisterPluginMetaResponse, err error) {
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	_authType, ok := model.ToAuthType(req.GetAuthType())
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("invalid auth type '%d'", req.GetAuthType())
 | |
| 	}
 | |
| 	authType := ptr.Of(_authType)
 | |
| 
 | |
| 	var authSubType *model.AuthzSubType
 | |
| 	if req.SubAuthType != nil {
 | |
| 		_authSubType, ok := model.ToAuthSubType(req.GetSubAuthType())
 | |
| 		if !ok {
 | |
| 			return nil, fmt.Errorf("invalid sub authz type '%d'", req.GetSubAuthType())
 | |
| 		}
 | |
| 		authSubType = ptr.Of(_authSubType)
 | |
| 	}
 | |
| 
 | |
| 	var loc model.HTTPParamLocation
 | |
| 	if *authType == model.AuthzTypeOfService {
 | |
| 		if req.GetLocation() == common.AuthorizationServiceLocation_Query {
 | |
| 			loc = model.ParamInQuery
 | |
| 		} else if req.GetLocation() == common.AuthorizationServiceLocation_Header {
 | |
| 			loc = model.ParamInPath
 | |
| 		} else {
 | |
| 			return nil, fmt.Errorf("invalid location '%s'", req.GetLocation())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	r := &service.CreateDraftPluginRequest{
 | |
| 		PluginType:   req.GetPluginType(),
 | |
| 		SpaceID:      req.GetSpaceID(),
 | |
| 		DeveloperID:  *userID,
 | |
| 		IconURI:      req.Icon.URI,
 | |
| 		ProjectID:    req.ProjectID,
 | |
| 		Name:         req.GetName(),
 | |
| 		Desc:         req.GetDesc(),
 | |
| 		ServerURL:    req.GetURL(),
 | |
| 		CommonParams: req.CommonParams,
 | |
| 		AuthInfo: &service.PluginAuthInfo{
 | |
| 			AuthzType:    authType,
 | |
| 			Location:     ptr.Of(loc),
 | |
| 			Key:          req.Key,
 | |
| 			ServiceToken: req.ServiceToken,
 | |
| 			OAuthInfo:    req.OauthInfo,
 | |
| 			AuthzSubType: authSubType,
 | |
| 			AuthzPayload: req.AuthPayload,
 | |
| 		},
 | |
| 	}
 | |
| 	pluginID, err := p.DomainSVC.CreateDraftPlugin(ctx, r)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "CreateDraftPlugin failed")
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Created,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:       resCommon.ResType_Plugin,
 | |
| 			ResSubType:    ptr.Of(int32(req.GetPluginType())),
 | |
| 			ResID:         pluginID,
 | |
| 			Name:          &req.Name,
 | |
| 			SpaceID:       &req.SpaceID,
 | |
| 			APPID:         req.ProjectID,
 | |
| 			OwnerID:       userID,
 | |
| 			PublishStatus: ptr.Of(resCommon.PublishStatus_UnPublished),
 | |
| 			CreateTimeMS:  ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("publish resource '%d' failed, err=%v", pluginID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.RegisterPluginMetaResponse{
 | |
| 		PluginID: pluginID,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) RegisterPlugin(ctx context.Context, req *pluginAPI.RegisterPluginRequest) (resp *pluginAPI.RegisterPluginResponse, err error) {
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	mf := &entity.PluginManifest{}
 | |
| 	err = sonic.UnmarshalString(req.AiPlugin, &mf)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginInvalidManifest, errorx.KV(errno.PluginMsgKey, err.Error()))
 | |
| 	}
 | |
| 
 | |
| 	mf.LogoURL = commonConsts.DefaultPluginIcon
 | |
| 
 | |
| 	doc, err := openapi3.NewLoader().LoadFromData([]byte(req.Openapi))
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginInvalidOpenapi3Doc, errorx.KV(errno.PluginMsgKey, err.Error()))
 | |
| 	}
 | |
| 
 | |
| 	res, err := p.DomainSVC.CreateDraftPluginWithCode(ctx, &service.CreateDraftPluginWithCodeRequest{
 | |
| 		SpaceID:     req.GetSpaceID(),
 | |
| 		DeveloperID: *userID,
 | |
| 		ProjectID:   req.ProjectID,
 | |
| 		Manifest:    mf,
 | |
| 		OpenapiDoc:  ptr.Of(model.Openapi3T(*doc)),
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "CreateDraftPluginWithCode failed")
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Created,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:       resCommon.ResType_Plugin,
 | |
| 			ResSubType:    ptr.Of(int32(res.Plugin.PluginType)),
 | |
| 			ResID:         res.Plugin.ID,
 | |
| 			Name:          ptr.Of(res.Plugin.GetName()),
 | |
| 			APPID:         req.ProjectID,
 | |
| 			SpaceID:       &req.SpaceID,
 | |
| 			OwnerID:       userID,
 | |
| 			PublishStatus: ptr.Of(resCommon.PublishStatus_UnPublished),
 | |
| 			CreateTimeMS:  ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("publish resource '%d' failed, err=%v", res.Plugin.ID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.RegisterPluginResponse{
 | |
| 		Data: &common.RegisterPluginData{
 | |
| 			PluginID: res.Plugin.ID,
 | |
| 			Openapi:  req.Openapi,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetPluginAPIs(ctx context.Context, req *pluginAPI.GetPluginAPIsRequest) (resp *pluginAPI.GetPluginAPIsResponse, err error) {
 | |
| 	pl, err := p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateGetPluginAPIsRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	var (
 | |
| 		draftTools []*entity.ToolInfo
 | |
| 		total      int64
 | |
| 	)
 | |
| 	if len(req.APIIds) > 0 {
 | |
| 		toolIDs := make([]int64, 0, len(req.APIIds))
 | |
| 		for _, id := range req.APIIds {
 | |
| 			toolID, err := strconv.ParseInt(id, 10, 64)
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("invalid tool id '%s'", id)
 | |
| 			}
 | |
| 			toolIDs = append(toolIDs, toolID)
 | |
| 		}
 | |
| 
 | |
| 		draftTools, err = p.toolRepo.MGetDraftTools(ctx, toolIDs)
 | |
| 		if err != nil {
 | |
| 			return nil, errorx.Wrapf(err, "MGetDraftTools failed, toolIDs=%v", toolIDs)
 | |
| 		}
 | |
| 
 | |
| 		total = int64(len(draftTools))
 | |
| 
 | |
| 	} else {
 | |
| 		pageInfo := entity.PageInfo{
 | |
| 			Page:       int(req.Page),
 | |
| 			Size:       int(req.Size),
 | |
| 			SortBy:     ptr.Of(entity.SortByCreatedAt),
 | |
| 			OrderByACS: ptr.Of(false),
 | |
| 		}
 | |
| 		draftTools, total, err = p.toolRepo.ListPluginDraftTools(ctx, req.PluginID, pageInfo)
 | |
| 		if err != nil {
 | |
| 			return nil, errorx.Wrapf(err, "ListPluginDraftTools failed, pluginID=%d", req.PluginID)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(draftTools) == 0 {
 | |
| 		return &pluginAPI.GetPluginAPIsResponse{
 | |
| 			APIInfo: make([]*common.PluginAPIInfo, 0),
 | |
| 			Total:   0,
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	draftToolIDs := slices.Transform(draftTools, func(tl *entity.ToolInfo) int64 {
 | |
| 		return tl.ID
 | |
| 	})
 | |
| 	onlineStatus, err := p.getToolOnlineStatus(ctx, draftToolIDs)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	apis := make([]*common.PluginAPIInfo, 0, len(draftTools))
 | |
| 	for _, tool := range draftTools {
 | |
| 		method, ok := model.ToThriftAPIMethod(tool.GetMethod())
 | |
| 		if !ok {
 | |
| 			return nil, fmt.Errorf("invalid method '%s'", tool.GetMethod())
 | |
| 		}
 | |
| 		reqParams, err := tool.ToReqAPIParameter()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		respParams, err := tool.ToRespAPIParameter()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		var apiExtend *common.APIExtend
 | |
| 		if tmp, ok := tool.Operation.Extensions[model.APISchemaExtendAuthMode].(string); ok {
 | |
| 			if mode, ok := model.ToThriftAPIAuthMode(model.ToolAuthMode(tmp)); ok {
 | |
| 				apiExtend = &common.APIExtend{
 | |
| 					AuthMode: mode,
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		api := &common.PluginAPIInfo{
 | |
| 			APIID:       strconv.FormatInt(tool.ID, 10),
 | |
| 			CreateTime:  strconv.FormatInt(tool.CreatedAt/1000, 10),
 | |
| 			DebugStatus: tool.GetDebugStatus(),
 | |
| 			Desc:        tool.GetDesc(),
 | |
| 			Disabled: func() bool {
 | |
| 				if tool.GetActivatedStatus() == model.DeactivateTool {
 | |
| 					return true
 | |
| 				}
 | |
| 				return false
 | |
| 			}(),
 | |
| 			Method:         method,
 | |
| 			Name:           tool.GetName(),
 | |
| 			OnlineStatus:   onlineStatus[tool.ID],
 | |
| 			Path:           tool.GetSubURL(),
 | |
| 			PluginID:       strconv.FormatInt(tool.PluginID, 10),
 | |
| 			RequestParams:  reqParams,
 | |
| 			ResponseParams: respParams,
 | |
| 			StatisticData:  common.NewPluginStatisticData(),
 | |
| 			APIExtend:      apiExtend,
 | |
| 		}
 | |
| 		example := pl.GetToolExample(ctx, tool.GetName())
 | |
| 		if example != nil {
 | |
| 			api.DebugExample = &common.DebugExample{
 | |
| 				ReqExample:  example.RequestExample,
 | |
| 				RespExample: example.ResponseExample,
 | |
| 			}
 | |
| 			api.DebugExampleStatus = common.DebugExampleStatus_Enable
 | |
| 		}
 | |
| 
 | |
| 		apis = append(apis, api)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetPluginAPIsResponse{
 | |
| 		APIInfo: apis,
 | |
| 		Total:   int32(total),
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getToolOnlineStatus(ctx context.Context, toolIDs []int64) (map[int64]common.OnlineStatus, error) {
 | |
| 	onlineTools, err := p.toolRepo.MGetOnlineTools(ctx, toolIDs, repository.WithToolID())
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "MGetOnlineTools failed, toolIDs=%v", toolIDs)
 | |
| 	}
 | |
| 
 | |
| 	onlineStatus := make(map[int64]common.OnlineStatus, len(onlineTools))
 | |
| 	for _, tool := range onlineTools {
 | |
| 		onlineStatus[tool.ID] = common.OnlineStatus_ONLINE
 | |
| 	}
 | |
| 
 | |
| 	return onlineStatus, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetPluginInfo(ctx context.Context, req *pluginAPI.GetPluginInfoRequest) (resp *pluginAPI.GetPluginInfoResponse, err error) {
 | |
| 	draftPlugin, err := p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateGetPluginInfoRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	metaInfo, err := p.getPluginMetaInfo(ctx, draftPlugin)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	codeInfo, err := p.getPluginCodeInfo(ctx, draftPlugin)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	_, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, req.PluginID, repository.WithPluginID())
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetPluginInfoResponse{
 | |
| 		MetaInfo:       metaInfo,
 | |
| 		CodeInfo:       codeInfo,
 | |
| 		Creator:        common.NewCreator(),
 | |
| 		StatisticData:  common.NewPluginStatisticData(),
 | |
| 		PluginType:     draftPlugin.PluginType,
 | |
| 		CreationMethod: common.CreationMethod_COZE,
 | |
| 		Published:      exist,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getPluginCodeInfo(ctx context.Context, draftPlugin *entity.PluginInfo) (*common.CodeInfo, error) {
 | |
| 	tools, err := p.toolRepo.GetPluginAllDraftTools(ctx, draftPlugin.ID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetPluginAllDraftTools failed, pluginID=%d", draftPlugin.ID)
 | |
| 	}
 | |
| 
 | |
| 	paths := openapi3.Paths{}
 | |
| 	for _, tool := range tools {
 | |
| 		if tool.GetActivatedStatus() == model.DeactivateTool {
 | |
| 			continue
 | |
| 		}
 | |
| 		item := &openapi3.PathItem{}
 | |
| 		item.SetOperation(tool.GetMethod(), tool.Operation.Operation)
 | |
| 		paths[tool.GetSubURL()] = item
 | |
| 	}
 | |
| 	draftPlugin.OpenapiDoc.Paths = paths
 | |
| 
 | |
| 	manifestStr, err := sonic.MarshalString(draftPlugin.Manifest)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("marshal manifest failed, err=%v", err)
 | |
| 	}
 | |
| 
 | |
| 	docBytes, err := yaml.Marshal(draftPlugin.OpenapiDoc)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("marshal openapi doc failed, err=%v", err)
 | |
| 	}
 | |
| 
 | |
| 	codeInfo := &common.CodeInfo{
 | |
| 		OpenapiDesc: string(docBytes),
 | |
| 		PluginDesc:  manifestStr,
 | |
| 	}
 | |
| 
 | |
| 	return codeInfo, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getPluginMetaInfo(ctx context.Context, draftPlugin *entity.PluginInfo) (*common.PluginMetaInfo, error) {
 | |
| 	commonParams := make(map[common.ParameterLocation][]*common.CommonParamSchema, len(draftPlugin.Manifest.CommonParams))
 | |
| 	for loc, params := range draftPlugin.Manifest.CommonParams {
 | |
| 		location, ok := model.ToThriftHTTPParamLocation(loc)
 | |
| 		if !ok {
 | |
| 			return nil, fmt.Errorf("invalid location '%s'", loc)
 | |
| 		}
 | |
| 		commonParams[location] = make([]*common.CommonParamSchema, 0, len(params))
 | |
| 		for _, param := range params {
 | |
| 			commonParams[location] = append(commonParams[location], &common.CommonParamSchema{
 | |
| 				Name:  param.Name,
 | |
| 				Value: param.Value,
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	iconURL, err := p.oss.GetObjectUrl(ctx, draftPlugin.GetIconURI())
 | |
| 	if err != nil {
 | |
| 		logs.CtxWarnf(ctx, "get icon url with '%s' failed, err=%v", draftPlugin.GetIconURI(), err)
 | |
| 	}
 | |
| 
 | |
| 	metaInfo := &common.PluginMetaInfo{
 | |
| 		Name: draftPlugin.GetName(),
 | |
| 		Desc: draftPlugin.GetDesc(),
 | |
| 		URL:  draftPlugin.GetServerURL(),
 | |
| 		Icon: &common.PluginIcon{
 | |
| 			URI: draftPlugin.GetIconURI(),
 | |
| 			URL: iconURL,
 | |
| 		},
 | |
| 		CommonParams: commonParams,
 | |
| 	}
 | |
| 
 | |
| 	err = p.fillAuthInfoInMetaInfo(ctx, draftPlugin, metaInfo)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "fillAuthInfoInMetaInfo failed, pluginID=%d", draftPlugin.ID)
 | |
| 	}
 | |
| 
 | |
| 	return metaInfo, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) fillAuthInfoInMetaInfo(ctx context.Context, draftPlugin *entity.PluginInfo, metaInfo *common.PluginMetaInfo) (err error) {
 | |
| 	authInfo := draftPlugin.GetAuthInfo()
 | |
| 	authType, ok := model.ToThriftAuthType(authInfo.Type)
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("invalid auth type '%s'", authInfo.Type)
 | |
| 	}
 | |
| 
 | |
| 	var subAuthType *int32
 | |
| 	if authInfo.SubType != "" {
 | |
| 		_subAuthType, ok := model.ToThriftAuthSubType(authInfo.SubType)
 | |
| 		if !ok {
 | |
| 			return fmt.Errorf("invalid sub authz type '%s'", authInfo.SubType)
 | |
| 		}
 | |
| 		subAuthType = &_subAuthType
 | |
| 	}
 | |
| 
 | |
| 	metaInfo.AuthType = append(metaInfo.AuthType, authType)
 | |
| 	metaInfo.SubAuthType = subAuthType
 | |
| 
 | |
| 	if authType == common.AuthorizationType_None {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if authType == common.AuthorizationType_Service {
 | |
| 		var loc common.AuthorizationServiceLocation
 | |
| 		_loc := model.HTTPParamLocation(strings.ToLower(string(authInfo.AuthOfAPIToken.Location)))
 | |
| 		if _loc == model.ParamInHeader {
 | |
| 			loc = common.AuthorizationServiceLocation_Header
 | |
| 		} else if _loc == model.ParamInQuery {
 | |
| 			loc = common.AuthorizationServiceLocation_Query
 | |
| 		} else {
 | |
| 			return fmt.Errorf("invalid location '%s'", authInfo.AuthOfAPIToken.Location)
 | |
| 		}
 | |
| 
 | |
| 		metaInfo.Location = ptr.Of(loc)
 | |
| 		metaInfo.Key = ptr.Of(authInfo.AuthOfAPIToken.Key)
 | |
| 		metaInfo.ServiceToken = ptr.Of(authInfo.AuthOfAPIToken.ServiceToken)
 | |
| 	}
 | |
| 
 | |
| 	if authType == common.AuthorizationType_OAuth {
 | |
| 		metaInfo.OauthInfo = &authInfo.Payload
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetUpdatedAPIs(ctx context.Context, req *pluginAPI.GetUpdatedAPIsRequest) (resp *pluginAPI.GetUpdatedAPIsResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateGetUpdatedAPIsRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	draftTools, err := p.toolRepo.GetPluginAllDraftTools(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetPluginAllDraftTools failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 	onlineTools, err := p.toolRepo.GetPluginAllOnlineTools(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetPluginAllOnlineTools failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	var updatedToolName, createdToolName, delToolName []string
 | |
| 
 | |
| 	draftMap := slices.ToMap(draftTools, func(e *entity.ToolInfo) (string, *entity.ToolInfo) {
 | |
| 		return e.GetName(), e
 | |
| 	})
 | |
| 	onlineMap := slices.ToMap(onlineTools, func(e *entity.ToolInfo) (string, *entity.ToolInfo) {
 | |
| 		return e.GetName(), e
 | |
| 	})
 | |
| 
 | |
| 	for name := range draftMap {
 | |
| 		if _, ok := onlineMap[name]; !ok {
 | |
| 			createdToolName = append(createdToolName, name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for name, ot := range onlineMap {
 | |
| 		dt, ok := draftMap[name]
 | |
| 		if !ok {
 | |
| 			delToolName = append(delToolName, name)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if ot.GetMethod() != dt.GetMethod() ||
 | |
| 			ot.GetSubURL() != dt.GetSubURL() ||
 | |
| 			ot.GetDesc() != dt.GetDesc() {
 | |
| 			updatedToolName = append(updatedToolName, name)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		os, err := sonic.MarshalString(ot.Operation)
 | |
| 		if err != nil {
 | |
| 			logs.CtxErrorf(ctx, "marshal online tool operation failed, toolID=%d, err=%v", ot.ID, err)
 | |
| 
 | |
| 			updatedToolName = append(updatedToolName, name)
 | |
| 			continue
 | |
| 		}
 | |
| 		ds, err := sonic.MarshalString(dt.Operation)
 | |
| 		if err != nil {
 | |
| 			logs.CtxErrorf(ctx, "marshal draft tool operation failed, toolID=%d, err=%v", ot.ID, err)
 | |
| 
 | |
| 			updatedToolName = append(updatedToolName, name)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if os != ds {
 | |
| 			updatedToolName = append(updatedToolName, name)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetUpdatedAPIsResponse{
 | |
| 		UpdatedAPINames: updatedToolName,
 | |
| 		CreatedAPINames: createdToolName,
 | |
| 		DeletedAPINames: delToolName,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetUserAuthority(ctx context.Context, req *pluginAPI.GetUserAuthorityRequest) (resp *pluginAPI.GetUserAuthorityResponse, err error) {
 | |
| 	resp = &pluginAPI.GetUserAuthorityResponse{
 | |
| 		Data: &common.GetUserAuthorityData{
 | |
| 			CanEdit:          true,
 | |
| 			CanRead:          true,
 | |
| 			CanDelete:        true,
 | |
| 			CanDebug:         true,
 | |
| 			CanPublish:       true,
 | |
| 			CanReadChangelog: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetOAuthStatus(ctx context.Context, req *pluginAPI.GetOAuthStatusRequest) (resp *pluginAPI.GetOAuthStatusResponse, err error) {
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrSearchPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	res, err := p.DomainSVC.GetOAuthStatus(ctx, *userID, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetOAuthStatus failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 	resp = &pluginAPI.GetOAuthStatusResponse{
 | |
| 		IsOauth: res.IsOauth,
 | |
| 		Status:  res.Status,
 | |
| 		Content: res.OAuthURL,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) CheckAndLockPluginEdit(ctx context.Context, req *pluginAPI.CheckAndLockPluginEditRequest) (resp *pluginAPI.CheckAndLockPluginEditResponse, err error) {
 | |
| 	resp = &pluginAPI.CheckAndLockPluginEditResponse{
 | |
| 		Data: &common.CheckAndLockPluginEditData{
 | |
| 			Seized: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) CreateAPI(ctx context.Context, req *pluginAPI.CreateAPIRequest) (resp *pluginAPI.CreateAPIResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateCreateAPIRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	defaultSubURL := gonanoid.MustID(6)
 | |
| 
 | |
| 	tool := &entity.ToolInfo{
 | |
| 		PluginID:        req.PluginID,
 | |
| 		ActivatedStatus: ptr.Of(model.ActivateTool),
 | |
| 		DebugStatus:     ptr.Of(common.APIDebugStatus_DebugWaiting),
 | |
| 		SubURL:          ptr.Of("/" + defaultSubURL),
 | |
| 		Method:          ptr.Of(http.MethodGet),
 | |
| 		Operation: model.NewOpenapi3Operation(&openapi3.Operation{
 | |
| 			Summary:     req.Desc,
 | |
| 			OperationID: req.Name,
 | |
| 			Parameters:  []*openapi3.ParameterRef{},
 | |
| 			RequestBody: entity.DefaultOpenapi3RequestBody(),
 | |
| 			Responses:   entity.DefaultOpenapi3Responses(),
 | |
| 			Extensions:  map[string]any{},
 | |
| 		}),
 | |
| 	}
 | |
| 
 | |
| 	toolID, err := p.toolRepo.CreateDraftTool(ctx, tool)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "CreateDraftTool failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.CreateAPIResponse{
 | |
| 		APIID: strconv.FormatInt(toolID, 10),
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) UpdateAPI(ctx context.Context, req *pluginAPI.UpdateAPIRequest) (resp *pluginAPI.UpdateAPIResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateUpdateAPIRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	op, err := pluginutil.APIParamsToOpenapiOperation(req.RequestParams, req.ResponseParams)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var method *string
 | |
| 	if m, ok := model.ToHTTPMethod(req.GetMethod()); ok {
 | |
| 		method = &m
 | |
| 	}
 | |
| 
 | |
| 	updateReq := &service.UpdateToolDraftRequest{
 | |
| 		PluginID:     req.PluginID,
 | |
| 		ToolID:       req.APIID,
 | |
| 		Name:         req.Name,
 | |
| 		Desc:         req.Desc,
 | |
| 		SubURL:       req.Path,
 | |
| 		Method:       method,
 | |
| 		Parameters:   op.Parameters,
 | |
| 		RequestBody:  op.RequestBody,
 | |
| 		Responses:    op.Responses,
 | |
| 		Disabled:     req.Disabled,
 | |
| 		SaveExample:  req.SaveExample,
 | |
| 		DebugExample: req.DebugExample,
 | |
| 		APIExtend:    req.APIExtend,
 | |
| 	}
 | |
| 	err = p.DomainSVC.UpdateDraftTool(ctx, updateReq)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "UpdateDraftTool failed, pluginID=%d, toolID=%d", updateReq.PluginID, updateReq.ToolID)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Updated,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:      resCommon.ResType_Plugin,
 | |
| 			ResID:        req.PluginID,
 | |
| 			UpdateTimeMS: ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "publish resource '%d' failed, err=%v", req.PluginID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.UpdateAPIResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) UpdatePlugin(ctx context.Context, req *pluginAPI.UpdatePluginRequest) (resp *pluginAPI.UpdatePluginResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateUpdatePluginRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 
 | |
| 	loader := openapi3.NewLoader()
 | |
| 	_doc, err := loader.LoadFromData([]byte(req.Openapi))
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginInvalidOpenapi3Doc, errorx.KV(errno.PluginMsgKey, err.Error()))
 | |
| 	}
 | |
| 
 | |
| 	doc := ptr.Of(model.Openapi3T(*_doc))
 | |
| 
 | |
| 	manifest := &entity.PluginManifest{}
 | |
| 	err = sonic.UnmarshalString(req.AiPlugin, manifest)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginInvalidManifest, errorx.KV(errno.PluginMsgKey, err.Error()))
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.UpdateDraftPluginWithCode(ctx, &service.UpdateDraftPluginWithCodeRequest{
 | |
| 		UserID:     *userID,
 | |
| 		PluginID:   req.PluginID,
 | |
| 		OpenapiDoc: doc,
 | |
| 		Manifest:   manifest,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "UpdateDraftPluginWithCode failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Updated,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:      resCommon.ResType_Plugin,
 | |
| 			ResID:        req.PluginID,
 | |
| 			Name:         &manifest.NameForHuman,
 | |
| 			UpdateTimeMS: ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "publish resource '%d' failed, err=%v", req.PluginID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.UpdatePluginResponse{
 | |
| 		Data: &common.UpdatePluginData{
 | |
| 			Res: true,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) DeleteAPI(ctx context.Context, req *pluginAPI.DeleteAPIRequest) (resp *pluginAPI.DeleteAPIResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateDeleteAPIRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	err = p.toolRepo.DeleteDraftTool(ctx, req.APIID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "DeleteDraftTool failed, toolID=%d", req.APIID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.DeleteAPIResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) DelPlugin(ctx context.Context, req *pluginAPI.DelPluginRequest) (resp *pluginAPI.DelPluginResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateDelPluginRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.DeleteDraftPlugin(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "DeleteDraftPlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Deleted,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:      resCommon.ResType_Plugin,
 | |
| 			ResID:        req.PluginID,
 | |
| 			UpdateTimeMS: ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "publish resource '%d' failed", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.DelPluginResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) PublishPlugin(ctx context.Context, req *pluginAPI.PublishPluginRequest) (resp *pluginAPI.PublishPluginResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validatePublishPluginRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.PublishPlugin(ctx, &service.PublishPluginRequest{
 | |
| 		PluginID:    req.PluginID,
 | |
| 		Version:     req.VersionName,
 | |
| 		VersionDesc: req.VersionDesc,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "PublishPlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Updated,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:       resCommon.ResType_Plugin,
 | |
| 			ResID:         req.PluginID,
 | |
| 			PublishStatus: ptr.Of(resCommon.PublishStatus_Published),
 | |
| 			PublishTimeMS: ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "publish resource '%d' failed, err=%v", req.PluginID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.PublishPluginResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) UpdatePluginMeta(ctx context.Context, req *pluginAPI.UpdatePluginMetaRequest) (resp *pluginAPI.UpdatePluginMetaResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateUpdatePluginMetaRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	authInfo, err := getUpdateAuthInfo(ctx, req)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	updateReq := &service.UpdateDraftPluginRequest{
 | |
| 		PluginID:     req.PluginID,
 | |
| 		Name:         req.Name,
 | |
| 		Desc:         req.Desc,
 | |
| 		URL:          req.URL,
 | |
| 		Icon:         req.Icon,
 | |
| 		CommonParams: req.CommonParams,
 | |
| 		AuthInfo:     authInfo,
 | |
| 	}
 | |
| 	err = p.DomainSVC.UpdateDraftPlugin(ctx, updateReq)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "UpdateDraftPlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Updated,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:      resCommon.ResType_Plugin,
 | |
| 			ResID:        req.PluginID,
 | |
| 			Name:         req.Name,
 | |
| 			UpdateTimeMS: ptr.Of(time.Now().UnixMilli()),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "publish resource '%d' failed, err=%v", req.PluginID, err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.UpdatePluginMetaResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func getUpdateAuthInfo(ctx context.Context, req *pluginAPI.UpdatePluginMetaRequest) (authInfo *service.PluginAuthInfo, err error) {
 | |
| 	if req.AuthType == nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	_authType, ok := model.ToAuthType(req.GetAuthType())
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("invalid auth type '%d'", req.GetAuthType())
 | |
| 	}
 | |
| 	authType := &_authType
 | |
| 
 | |
| 	var authSubType *model.AuthzSubType
 | |
| 	if req.SubAuthType != nil {
 | |
| 		_authSubType, ok := model.ToAuthSubType(req.GetSubAuthType())
 | |
| 		if !ok {
 | |
| 			return nil, fmt.Errorf("invalid sub authz type '%d'", req.GetSubAuthType())
 | |
| 		}
 | |
| 		authSubType = &_authSubType
 | |
| 	}
 | |
| 
 | |
| 	var location *model.HTTPParamLocation
 | |
| 	if req.Location != nil {
 | |
| 		if *req.Location == common.AuthorizationServiceLocation_Header {
 | |
| 			location = ptr.Of(model.ParamInHeader)
 | |
| 		} else if *req.Location == common.AuthorizationServiceLocation_Query {
 | |
| 			location = ptr.Of(model.ParamInQuery)
 | |
| 		} else {
 | |
| 			return nil, fmt.Errorf("invalid location '%d'", req.GetLocation())
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	authInfo = &service.PluginAuthInfo{
 | |
| 		AuthzType:    authType,
 | |
| 		Location:     location,
 | |
| 		Key:          req.Key,
 | |
| 		ServiceToken: req.ServiceToken,
 | |
| 		OAuthInfo:    req.OauthInfo,
 | |
| 		AuthzSubType: authSubType,
 | |
| 		AuthzPayload: req.AuthPayload,
 | |
| 	}
 | |
| 
 | |
| 	return authInfo, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetBotDefaultParams(ctx context.Context, req *pluginAPI.GetBotDefaultParamsRequest) (resp *pluginAPI.GetBotDefaultParamsResponse, err error) {
 | |
| 	_, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, req.PluginID, repository.WithPluginID())
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 	if !exist {
 | |
| 		return nil, errorx.New(errno.ErrPluginRecordNotFound)
 | |
| 	}
 | |
| 
 | |
| 	draftAgentTool, err := p.DomainSVC.GetDraftAgentToolByName(ctx, req.BotID, req.APIName)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetDraftAgentToolByName failed, agentID=%d, toolName=%s", req.BotID, req.APIName)
 | |
| 	}
 | |
| 
 | |
| 	reqAPIParams, err := draftAgentTool.ToReqAPIParameter()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	respAPIParams, err := draftAgentTool.ToRespAPIParameter()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetBotDefaultParamsResponse{
 | |
| 		RequestParams:  reqAPIParams,
 | |
| 		ResponseParams: respAPIParams,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) UpdateBotDefaultParams(ctx context.Context, req *pluginAPI.UpdateBotDefaultParamsRequest) (resp *pluginAPI.UpdateBotDefaultParamsResponse, err error) {
 | |
| 	op, err := pluginutil.APIParamsToOpenapiOperation(req.RequestParams, req.ResponseParams)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.UpdateBotDefaultParams(ctx, &service.UpdateBotDefaultParamsRequest{
 | |
| 		PluginID:    req.PluginID,
 | |
| 		ToolName:    req.APIName,
 | |
| 		AgentID:     req.BotID,
 | |
| 		Parameters:  op.Parameters,
 | |
| 		RequestBody: op.RequestBody,
 | |
| 		Responses:   op.Responses,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "UpdateBotDefaultParams failed, agentID=%d, toolName=%s", req.BotID, req.APIName)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.UpdateBotDefaultParamsResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) DebugAPI(ctx context.Context, req *pluginAPI.DebugAPIRequest) (resp *pluginAPI.DebugAPIResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateDebugAPIRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	const defaultErrReason = "internal server error"
 | |
| 
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.DebugAPIResponse{
 | |
| 		Success: false,
 | |
| 		RawReq:  "{}",
 | |
| 		RawResp: "{}",
 | |
| 		Resp:    "{}",
 | |
| 	}
 | |
| 
 | |
| 	res, err := p.DomainSVC.ExecuteTool(ctx, &service.ExecuteToolRequest{
 | |
| 		UserID:          conv.Int64ToStr(*userID),
 | |
| 		PluginID:        req.PluginID,
 | |
| 		ToolID:          req.APIID,
 | |
| 		ExecScene:       model.ExecSceneOfToolDebug,
 | |
| 		ExecDraftTool:   true,
 | |
| 		ArgumentsInJson: req.Parameters,
 | |
| 	}, model.WithAutoGenRespSchema())
 | |
| 	if err != nil {
 | |
| 		var e errorx.StatusError
 | |
| 		if errors.As(err, &e) {
 | |
| 			resp.Reason = e.Msg()
 | |
| 			return resp, nil
 | |
| 		}
 | |
| 
 | |
| 		logs.CtxErrorf(ctx, "ExecuteTool failed, err=%v", err)
 | |
| 		resp.Reason = defaultErrReason
 | |
| 
 | |
| 		return resp, nil
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.DebugAPIResponse{
 | |
| 		Success:        true,
 | |
| 		Resp:           res.TrimmedResp,
 | |
| 		RawReq:         res.Request,
 | |
| 		RawResp:        res.RawResp,
 | |
| 		ResponseParams: []*common.APIParameter{},
 | |
| 	}
 | |
| 
 | |
| 	if req.Operation == common.DebugOperation_Parse {
 | |
| 		res.Tool.Operation.Responses = res.RespSchema
 | |
| 	}
 | |
| 
 | |
| 	respParams, err := res.Tool.ToRespAPIParameter()
 | |
| 	if err != nil {
 | |
| 		logs.CtxErrorf(ctx, "ToRespAPIParameter failed, err=%v", err)
 | |
| 		resp.Success = false
 | |
| 		resp.Reason = defaultErrReason
 | |
| 	} else {
 | |
| 		resp.ResponseParams = respParams
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) UnlockPluginEdit(ctx context.Context, req *pluginAPI.UnlockPluginEditRequest) (resp *pluginAPI.UnlockPluginEditResponse, err error) {
 | |
| 	resp = &pluginAPI.UnlockPluginEditResponse{
 | |
| 		Released: true,
 | |
| 	}
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) PublicGetProductList(ctx context.Context, req *productAPI.GetProductListRequest) (resp *productAPI.GetProductListResponse, err error) {
 | |
| 	res, err := p.DomainSVC.ListPluginProducts(ctx, &service.ListPluginProductsRequest{})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "ListPluginProducts failed")
 | |
| 	}
 | |
| 
 | |
| 	products := make([]*productAPI.ProductInfo, 0, len(res.Plugins))
 | |
| 	for _, pl := range res.Plugins {
 | |
| 		tls, err := p.toolRepo.GetPluginAllOnlineTools(ctx, pl.ID)
 | |
| 		if err != nil {
 | |
| 			return nil, errorx.Wrapf(err, "GetPluginAllOnlineTools failed, pluginID=%d", pl.ID)
 | |
| 		}
 | |
| 
 | |
| 		pi, err := p.buildProductInfo(ctx, pl, tls)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		products = append(products, pi)
 | |
| 	}
 | |
| 
 | |
| 	if req.GetKeyword() != "" {
 | |
| 		filterProducts := make([]*productAPI.ProductInfo, 0, len(products))
 | |
| 		for _, _p := range products {
 | |
| 			if strings.Contains(strings.ToLower(_p.MetaInfo.Name), strings.ToLower(req.GetKeyword())) {
 | |
| 				filterProducts = append(filterProducts, _p)
 | |
| 			}
 | |
| 		}
 | |
| 		products = filterProducts
 | |
| 	}
 | |
| 
 | |
| 	resp = &productAPI.GetProductListResponse{
 | |
| 		Data: &productAPI.GetProductListData{
 | |
| 			Products: products,
 | |
| 			HasMore:  false, // 一次性拉完
 | |
| 			Total:    int32(res.Total),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) buildProductInfo(ctx context.Context, plugin *entity.PluginInfo, tools []*entity.ToolInfo) (*productAPI.ProductInfo, error) {
 | |
| 	metaInfo, err := p.buildProductMetaInfo(ctx, plugin)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	extraInfo, err := p.buildPluginProductExtraInfo(ctx, plugin, tools)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	pi := &productAPI.ProductInfo{
 | |
| 		CommercialSetting: &productCommon.CommercialSetting{
 | |
| 			CommercialType: productCommon.ProductPaidType_Free,
 | |
| 		},
 | |
| 		MetaInfo:    metaInfo,
 | |
| 		PluginExtra: extraInfo,
 | |
| 	}
 | |
| 
 | |
| 	return pi, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) buildProductMetaInfo(ctx context.Context, plugin *entity.PluginInfo) (*productAPI.ProductMetaInfo, error) {
 | |
| 	iconURL, err := p.oss.GetObjectUrl(ctx, plugin.GetIconURI())
 | |
| 	if err != nil {
 | |
| 		logs.CtxWarnf(ctx, "get icon url failed with '%s', err=%v", plugin.GetIconURI(), err)
 | |
| 	}
 | |
| 
 | |
| 	return &productAPI.ProductMetaInfo{
 | |
| 		ID:          plugin.GetRefProductID(),
 | |
| 		EntityID:    plugin.ID,
 | |
| 		EntityType:  productCommon.ProductEntityType_Plugin,
 | |
| 		IconURL:     iconURL,
 | |
| 		Name:        plugin.GetName(),
 | |
| 		Description: plugin.GetDesc(),
 | |
| 		IsFree:      true,
 | |
| 		IsOfficial:  true,
 | |
| 		Status:      productCommon.ProductStatus_Listed,
 | |
| 		ListedAt:    time.Now().Unix(),
 | |
| 		UserInfo: &productCommon.UserInfo{
 | |
| 			Name: "Coze Official",
 | |
| 		},
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) buildPluginProductExtraInfo(ctx context.Context, plugin *entity.PluginInfo, tools []*entity.ToolInfo) (*productAPI.PluginExtraInfo, error) {
 | |
| 	ei := &productAPI.PluginExtraInfo{
 | |
| 		IsOfficial: true,
 | |
| 		PluginType: func() *productCommon.PluginType {
 | |
| 			if plugin.PluginType == common.PluginType_LOCAL {
 | |
| 				return ptr.Of(productCommon.PluginType_LocalPlugin)
 | |
| 			}
 | |
| 			return ptr.Of(productCommon.PluginType_CLoudPlugin)
 | |
| 		}(),
 | |
| 	}
 | |
| 
 | |
| 	toolInfos := make([]*productAPI.PluginToolInfo, 0, len(tools))
 | |
| 	for _, tl := range tools {
 | |
| 		params, err := tl.ToToolParameters()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		toolInfo := &productAPI.PluginToolInfo{
 | |
| 			ID:          tl.ID,
 | |
| 			Name:        tl.GetName(),
 | |
| 			Description: tl.GetDesc(),
 | |
| 			Parameters:  params,
 | |
| 		}
 | |
| 
 | |
| 		example := plugin.GetToolExample(ctx, tl.GetName())
 | |
| 		if example != nil {
 | |
| 			toolInfo.Example = &productAPI.PluginToolExample{
 | |
| 				ReqExample:  example.RequestExample,
 | |
| 				RespExample: example.ResponseExample,
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		toolInfos = append(toolInfos, toolInfo)
 | |
| 	}
 | |
| 
 | |
| 	ei.Tools = toolInfos
 | |
| 
 | |
| 	authInfo := plugin.GetAuthInfo()
 | |
| 
 | |
| 	authMode := ptr.Of(productAPI.PluginAuthMode_NoAuth)
 | |
| 	if authInfo != nil {
 | |
| 		if authInfo.Type == model.AuthzTypeOfService || authInfo.Type == model.AuthzTypeOfOAuth {
 | |
| 			authMode = ptr.Of(productAPI.PluginAuthMode_Required)
 | |
| 			err := plugin.Manifest.Validate(false)
 | |
| 			if err != nil {
 | |
| 				logs.CtxWarnf(ctx, "validate plugin manifest failed, err=%v", err)
 | |
| 			} else {
 | |
| 				authMode = ptr.Of(productAPI.PluginAuthMode_Configured)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ei.AuthMode = authMode
 | |
| 
 | |
| 	return ei, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) PublicGetProductDetail(ctx context.Context, req *productAPI.GetProductDetailRequest) (resp *productAPI.GetProductDetailResponse, err error) {
 | |
| 	plugin, exist, err := p.pluginRepo.GetOnlinePlugin(ctx, req.GetEntityID())
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", req.GetEntityID())
 | |
| 	}
 | |
| 	if !exist {
 | |
| 		return nil, errorx.New(errno.ErrPluginRecordNotFound)
 | |
| 	}
 | |
| 
 | |
| 	tools, err := p.toolRepo.GetPluginAllOnlineTools(ctx, plugin.ID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetPluginAllOnlineTools failed, pluginID=%d", plugin.ID)
 | |
| 	}
 | |
| 	pi, err := p.buildProductInfo(ctx, plugin, tools)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	resp = &productAPI.GetProductDetailResponse{
 | |
| 		Data: &productAPI.GetProductDetailData{
 | |
| 			MetaInfo:    pi.MetaInfo,
 | |
| 			PluginExtra: pi.PluginExtra,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetPluginNextVersion(ctx context.Context, req *pluginAPI.GetPluginNextVersionRequest) (resp *pluginAPI.GetPluginNextVersionResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateGetPluginNextVersionRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	nextVersion, err := p.DomainSVC.GetPluginNextVersion(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetPluginNextVersion failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 	resp = &pluginAPI.GetPluginNextVersionResponse{
 | |
| 		NextVersionName: nextVersion,
 | |
| 	}
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetDevPluginList(ctx context.Context, req *pluginAPI.GetDevPluginListRequest) (resp *pluginAPI.GetDevPluginListResponse, err error) {
 | |
| 	pageInfo := entity.PageInfo{
 | |
| 		Name:       req.Name,
 | |
| 		Page:       int(req.GetPage()),
 | |
| 		Size:       int(req.GetSize()),
 | |
| 		OrderByACS: ptr.Of(false),
 | |
| 	}
 | |
| 	if req.GetOrderBy() == common.OrderBy_UpdateTime {
 | |
| 		pageInfo.SortBy = ptr.Of(entity.SortByUpdatedAt)
 | |
| 	} else {
 | |
| 		pageInfo.SortBy = ptr.Of(entity.SortByCreatedAt)
 | |
| 	}
 | |
| 
 | |
| 	res, err := p.DomainSVC.ListDraftPlugins(ctx, &service.ListDraftPluginsRequest{
 | |
| 		SpaceID:  req.SpaceID,
 | |
| 		APPID:    req.ProjectID,
 | |
| 		PageInfo: pageInfo,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "ListDraftPlugins failed, spaceID=%d, appID=%d", req.SpaceID, req.ProjectID)
 | |
| 	}
 | |
| 
 | |
| 	pluginList := make([]*common.PluginInfoForPlayground, 0, len(res.Plugins))
 | |
| 	for _, pl := range res.Plugins {
 | |
| 		tools, err := p.toolRepo.GetPluginAllDraftTools(ctx, pl.ID)
 | |
| 		if err != nil {
 | |
| 			return nil, errorx.Wrapf(err, "GetPluginAllDraftTools failed, pluginID=%d", pl.ID)
 | |
| 		}
 | |
| 
 | |
| 		pluginInfo, err := p.toPluginInfoForPlayground(ctx, pl, tools)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		pluginInfo.VersionTs = "0" // when you get the plugin information in the project, version ts is set to 0 by default
 | |
| 		pluginList = append(pluginList, pluginInfo)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetDevPluginListResponse{
 | |
| 		PluginList: pluginList,
 | |
| 		Total:      res.Total,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) getDevPluginListByName(ctx context.Context, req *pluginAPI.GetDevPluginListRequest) (pluginList []*common.PluginInfoForPlayground, total int64, err error) {
 | |
| 	limit := req.GetSize()
 | |
| 	if limit == 0 {
 | |
| 		limit = 10
 | |
| 	}
 | |
| 
 | |
| 	res, err := crosssearch.DefaultSVC().SearchResources(ctx, &searchModel.SearchResourcesRequest{
 | |
| 		SpaceID:  req.SpaceID,
 | |
| 		APPID:    req.ProjectID,
 | |
| 		Name:     req.GetName(),
 | |
| 		OrderAsc: false,
 | |
| 		ResTypeFilter: []resCommon.ResType{
 | |
| 			resCommon.ResType_Plugin,
 | |
| 		},
 | |
| 		Page:  req.Page,
 | |
| 		Limit: limit,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, 0, errorx.Wrapf(err, "SearchResources failed, spaceID=%d, appID=%d", req.SpaceID, req.ProjectID)
 | |
| 	}
 | |
| 
 | |
| 	pluginList = make([]*common.PluginInfoForPlayground, 0, len(res.Data))
 | |
| 	for _, pl := range res.Data {
 | |
| 		draftPlugin, exist, err := p.pluginRepo.GetDraftPlugin(ctx, pl.ResID)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, errorx.Wrapf(err, "GetDraftPlugin failed, pluginID=%d", pl.ResID)
 | |
| 		}
 | |
| 		if !exist {
 | |
| 			logs.CtxWarnf(ctx, "plugin not exist, pluginID=%d", pl.ResID)
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		tools, err := p.toolRepo.GetPluginAllDraftTools(ctx, draftPlugin.ID)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, errorx.Wrapf(err, "GetPluginAllDraftTools failed, pluginID=%d", draftPlugin.ID)
 | |
| 		}
 | |
| 
 | |
| 		pluginInfo, err := p.toPluginInfoForPlayground(ctx, draftPlugin, tools)
 | |
| 		if err != nil {
 | |
| 			return nil, 0, err
 | |
| 		}
 | |
| 
 | |
| 		pluginInfo.VersionTs = "0" // when you get the plugin information in the project, version ts is set to 0 by default
 | |
| 		pluginList = append(pluginList, pluginInfo)
 | |
| 	}
 | |
| 
 | |
| 	if res.TotalHits != nil {
 | |
| 		total = *res.TotalHits
 | |
| 	}
 | |
| 
 | |
| 	return pluginList, total, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) DeleteAPPAllPlugins(ctx context.Context, appID int64) (err error) {
 | |
| 	pluginIDs, err := p.DomainSVC.DeleteAPPAllPlugins(ctx, appID)
 | |
| 	if err != nil {
 | |
| 		return errorx.Wrapf(err, "DeleteAPPAllPlugins failed, appID=%d", appID)
 | |
| 	}
 | |
| 
 | |
| 	for _, id := range pluginIDs {
 | |
| 		err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 			OpType: searchEntity.Deleted,
 | |
| 			Resource: &searchEntity.ResourceDocument{
 | |
| 				ResType: resCommon.ResType_Plugin,
 | |
| 				ResID:   id,
 | |
| 			},
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return errorx.Wrapf(err, "publish resource '%d' failed", id)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) Convert2OpenAPI(ctx context.Context, req *pluginAPI.Convert2OpenAPIRequest) (resp *pluginAPI.Convert2OpenAPIResponse, err error) {
 | |
| 	res := p.DomainSVC.ConvertToOpenapi3Doc(ctx, &service.ConvertToOpenapi3DocRequest{
 | |
| 		RawInput:        req.Data,
 | |
| 		PluginServerURL: req.PluginURL,
 | |
| 	})
 | |
| 
 | |
| 	if res.ErrMsg != "" {
 | |
| 		return &pluginAPI.Convert2OpenAPIResponse{
 | |
| 			Code:              errno.ErrPluginInvalidThirdPartyCode,
 | |
| 			Msg:               res.ErrMsg,
 | |
| 			DuplicateAPIInfos: []*common.DuplicateAPIInfo{},
 | |
| 			PluginDataFormat:  ptr.Of(res.Format),
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	doc, err := yaml.Marshal(res.OpenapiDoc)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("marshal openapi doc failed, err=%v", err)
 | |
| 	}
 | |
| 	mf, err := json.Marshal(res.Manifest)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("marshal manifest failed, err=%v", err)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.Convert2OpenAPIResponse{
 | |
| 		PluginDataFormat:  ptr.Of(res.Format),
 | |
| 		Openapi:           ptr.Of(string(doc)),
 | |
| 		AiPlugin:          ptr.Of(string(mf)),
 | |
| 		DuplicateAPIInfos: []*common.DuplicateAPIInfo{},
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) BatchCreateAPI(ctx context.Context, req *pluginAPI.BatchCreateAPIRequest) (resp *pluginAPI.BatchCreateAPIResponse, err error) {
 | |
| 	_, err = p.validateDraftPluginAccess(ctx, req.PluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "validateBatchCreateAPIRequest failed")
 | |
| 	}
 | |
| 
 | |
| 	loader := openapi3.NewLoader()
 | |
| 	doc, err := loader.LoadFromData([]byte(req.Openapi))
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginInvalidOpenapi3Doc, errorx.KV(errno.PluginMsgKey, err.Error()))
 | |
| 	}
 | |
| 
 | |
| 	res, err := p.DomainSVC.CreateDraftToolsWithCode(ctx, &service.CreateDraftToolsWithCodeRequest{
 | |
| 		PluginID:          req.PluginID,
 | |
| 		OpenapiDoc:        ptr.Of(model.Openapi3T(*doc)),
 | |
| 		ConflictAndUpdate: req.ReplaceSamePaths,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "CreateDraftToolsWithCode failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	duplicated := slices.Transform(res.DuplicatedTools, func(e entity.UniqueToolAPI) *common.PluginAPIInfo {
 | |
| 		method, _ := model.ToThriftAPIMethod(e.Method)
 | |
| 		return &common.PluginAPIInfo{
 | |
| 			Path:   e.SubURL,
 | |
| 			Method: method,
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	resp = &pluginAPI.BatchCreateAPIResponse{
 | |
| 		PathsDuplicated: duplicated,
 | |
| 	}
 | |
| 
 | |
| 	if len(duplicated) > 0 {
 | |
| 		resp.Code = errno.ErrPluginDuplicatedTool
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) RevokeAuthToken(ctx context.Context, req *pluginAPI.RevokeAuthTokenRequest) (resp *pluginAPI.RevokeAuthTokenResponse, err error) {
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.RevokeAccessToken(ctx, &entity.AuthorizationCodeMeta{
 | |
| 		UserID:   conv.Int64ToStr(*userID),
 | |
| 		PluginID: req.PluginID,
 | |
| 		IsDraft:  req.GetBotID() == 0,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "RevokeAccessToken failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.RevokeAuthTokenResponse{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) CopyPlugin(ctx context.Context, req *CopyPluginRequest) (resp *CopyPluginResponse, err error) {
 | |
| 	res, err := p.DomainSVC.CopyPlugin(ctx, &service.CopyPluginRequest{
 | |
| 		UserID:      req.UserID,
 | |
| 		PluginID:    req.PluginID,
 | |
| 		CopyScene:   req.CopyScene,
 | |
| 		TargetAPPID: req.TargetAPPID,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "CopyPlugin failed, pluginID=%d", req.PluginID)
 | |
| 	}
 | |
| 
 | |
| 	plugin := res.Plugin
 | |
| 
 | |
| 	now := time.Now().UnixMilli()
 | |
| 	resDoc := &searchEntity.ResourceDocument{
 | |
| 		ResType:       resCommon.ResType_Plugin,
 | |
| 		ResSubType:    ptr.Of(int32(plugin.PluginType)),
 | |
| 		ResID:         plugin.ID,
 | |
| 		Name:          ptr.Of(plugin.GetName()),
 | |
| 		SpaceID:       &plugin.SpaceID,
 | |
| 		APPID:         plugin.APPID,
 | |
| 		OwnerID:       &req.UserID,
 | |
| 		PublishStatus: ptr.Of(resCommon.PublishStatus_UnPublished),
 | |
| 		CreateTimeMS:  ptr.Of(now),
 | |
| 	}
 | |
| 	if plugin.Published() {
 | |
| 		resDoc.PublishStatus = ptr.Of(resCommon.PublishStatus_Published)
 | |
| 		resDoc.PublishTimeMS = ptr.Of(now)
 | |
| 	}
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType:   searchEntity.Created,
 | |
| 		Resource: resDoc,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "publish resource '%d' failed", plugin.ID)
 | |
| 	}
 | |
| 
 | |
| 	resp = &CopyPluginResponse{
 | |
| 		Plugin: res.Plugin,
 | |
| 		Tools:  res.Tools,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) MoveAPPPluginToLibrary(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error) {
 | |
| 	plugin, err = p.DomainSVC.MoveAPPPluginToLibrary(ctx, pluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "MoveAPPPluginToLibrary failed, pluginID=%d", pluginID)
 | |
| 	}
 | |
| 
 | |
| 	now := time.Now().UnixMilli()
 | |
| 
 | |
| 	err = p.eventbus.PublishResources(ctx, &searchEntity.ResourceDomainEvent{
 | |
| 		OpType: searchEntity.Updated,
 | |
| 		Resource: &searchEntity.ResourceDocument{
 | |
| 			ResType:       resCommon.ResType_Plugin,
 | |
| 			ResID:         pluginID,
 | |
| 			APPID:         ptr.Of(int64(0)),
 | |
| 			PublishStatus: ptr.Of(resCommon.PublishStatus_Published),
 | |
| 			PublishTimeMS: ptr.Of(now),
 | |
| 			UpdateTimeMS:  ptr.Of(now),
 | |
| 		},
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "publish resource '%d' failed", pluginID)
 | |
| 	}
 | |
| 
 | |
| 	return plugin, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) validateDraftPluginAccess(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error) {
 | |
| 	uid := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if uid == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	plugin, err = p.DomainSVC.GetDraftPlugin(ctx, pluginID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetDraftPlugin failed, pluginID=%d", pluginID)
 | |
| 	}
 | |
| 
 | |
| 	if plugin.DeveloperID != *uid {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "you are not the plugin owner"))
 | |
| 	}
 | |
| 
 | |
| 	return plugin, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) OauthAuthorizationCode(ctx context.Context, req *botOpenAPI.OauthAuthorizationCodeReq) (resp *botOpenAPI.OauthAuthorizationCodeResp, err error) {
 | |
| 	stateStr, err := url.QueryUnescape(req.State)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.WrapByCode(err, errno.ErrPluginOAuthFailed, errorx.KV(errno.PluginMsgKey, "invalid state"))
 | |
| 	}
 | |
| 
 | |
| 	stateBytes, err := utils.DecryptByAES(stateStr, utils.StateSecretKey)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.WrapByCode(err, errno.ErrPluginOAuthFailed, errorx.KV(errno.PluginMsgKey, "invalid state"))
 | |
| 	}
 | |
| 
 | |
| 	state := &entity.OAuthState{}
 | |
| 	err = json.Unmarshal(stateBytes, state)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.WrapByCode(err, errno.ErrPluginOAuthFailed, errorx.KV(errno.PluginMsgKey, "invalid state"))
 | |
| 	}
 | |
| 
 | |
| 	err = p.DomainSVC.OAuthCode(ctx, req.Code, state)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.WrapByCode(err, errno.ErrPluginOAuthFailed, errorx.KV(errno.PluginMsgKey, "authorize failed"))
 | |
| 	}
 | |
| 
 | |
| 	resp = &botOpenAPI.OauthAuthorizationCodeResp{}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 | |
| 
 | |
| func (p *PluginApplicationService) GetQueriedOAuthPluginList(ctx context.Context, req *pluginAPI.GetQueriedOAuthPluginListRequest) (resp *pluginAPI.GetQueriedOAuthPluginListResponse, err error) {
 | |
| 	userID := ctxutil.GetUIDFromCtx(ctx)
 | |
| 	if userID == nil {
 | |
| 		return nil, errorx.New(errno.ErrPluginPermissionCode, errorx.KV(errno.PluginMsgKey, "session is required"))
 | |
| 	}
 | |
| 
 | |
| 	status, err := p.DomainSVC.GetAgentPluginsOAuthStatus(ctx, *userID, req.BotID)
 | |
| 	if err != nil {
 | |
| 		return nil, errorx.Wrapf(err, "GetAgentPluginsOAuthStatus failed, userID=%d, agentID=%d", *userID, req.BotID)
 | |
| 	}
 | |
| 
 | |
| 	if len(status) == 0 {
 | |
| 		return &pluginAPI.GetQueriedOAuthPluginListResponse{
 | |
| 			OauthPluginList: []*pluginAPI.OAuthPluginInfo{},
 | |
| 		}, nil
 | |
| 	}
 | |
| 
 | |
| 	oauthPluginList := make([]*pluginAPI.OAuthPluginInfo, 0, len(status))
 | |
| 	for _, s := range status {
 | |
| 		oauthPluginList = append(oauthPluginList, &pluginAPI.OAuthPluginInfo{
 | |
| 			PluginID:   s.PluginID,
 | |
| 			Status:     s.Status,
 | |
| 			Name:       s.PluginName,
 | |
| 			PluginIcon: s.PluginIconURL,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	resp = &pluginAPI.GetQueriedOAuthPluginListResponse{
 | |
| 		OauthPluginList: oauthPluginList,
 | |
| 	}
 | |
| 
 | |
| 	return resp, nil
 | |
| }
 |