Files
coze-studio/backend/application/plugin/plugin.go

317 lines
10 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"
"strings"
"time"
productCommon "github.com/coze-dev/coze-studio/backend/api/model/marketplace/product_common"
productAPI "github.com/coze-dev/coze-studio/backend/api/model/marketplace/product_public_api"
pluginAPI "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop"
common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/consts"
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/convert/api"
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
"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"
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/ptr"
"github.com/coze-dev/coze-studio/backend/pkg/logs"
"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) 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) 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 := api.APIParamsToOpenapiOperation(req.RequestParams, req.ResponseParams)
if err != nil {
return nil, err
}
err = p.DomainSVC.UpdateBotDefaultParams(ctx, &dto.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) 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, &dto.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, // Finish at one time
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 == consts.AuthzTypeOfService || authInfo.Type == consts.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) 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
}