6bed393c12
Backend Tests / backend-unit-test (push) Has been cancelled
Backend Tests / benchmark-test (push) Has been cancelled
CI@main / Node.js v22 (ubuntu-latest) (push) Has been cancelled
Thrift Syntax Validation / validate-thrift (push) Has been cancelled
License Check / License Check (push) Has been cancelled
303 lines
9.1 KiB
Go
303 lines
9.1 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 service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
|
|
|
pluginCommon "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
|
"github.com/coze-dev/coze-studio/backend/crossdomain/plugin/consts"
|
|
"github.com/coze-dev/coze-studio/backend/crossdomain/plugin/model"
|
|
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
|
|
domainDto "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/pkg/errorx"
|
|
"github.com/coze-dev/coze-studio/backend/types/errno"
|
|
|
|
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
|
"github.com/coze-dev/coze-studio/backend/pkg/saasapi"
|
|
)
|
|
|
|
type CozePlugin struct {
|
|
PluginID string `json:"plugin_id"`
|
|
Name string `json:"name"`
|
|
NameForModel string `json:"name_for_model"`
|
|
Description string `json:"description"`
|
|
IconURL string `json:"icon_url"`
|
|
Category string `json:"category"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
IsCallAvailable bool `json:"is_call_available"`
|
|
}
|
|
|
|
func (p *pluginServiceImpl) ListSaasPluginProducts(ctx context.Context, req *domainDto.ListSaasPluginProductsRequest) (resp *domainDto.ListPluginProductsResponse, err error) {
|
|
searchReq := &domainDto.SearchSaasPluginRequest{
|
|
PageNum: ptr.Of(int(*req.PageNum)),
|
|
PageSize: ptr.Of(int(*req.PageSize)),
|
|
Keyword: req.Keyword,
|
|
IsOfficial: req.IsOfficial,
|
|
ProductPaidType: req.ProductPaidType,
|
|
}
|
|
if len(req.CategoryIDs) > 0 {
|
|
searchReq.CategoryIDs = req.CategoryIDs
|
|
}
|
|
plugins, hasMore, err := p.fetchSaasPluginsFromCoze(ctx, searchReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &domainDto.ListPluginProductsResponse{
|
|
Plugins: plugins,
|
|
Total: int64(len(plugins)),
|
|
HasMore: hasMore,
|
|
}, nil
|
|
}
|
|
func (p *pluginServiceImpl) BatchGetSaasPluginToolsInfo(ctx context.Context, pluginIDs []int64) (tools map[int64][]*entity.ToolInfo, plugins map[int64]*entity.PluginInfo, err error) {
|
|
return p.toolRepo.BatchGetSaasPluginToolsInfo(ctx, pluginIDs)
|
|
}
|
|
|
|
func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context, searchReq *domainDto.SearchSaasPluginRequest) ([]*entity.PluginInfo, bool, error) {
|
|
|
|
searchResp, err := p.searchSaasPlugin(ctx, searchReq)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if searchResp == nil || searchResp.Data == nil {
|
|
return nil, false, nil
|
|
}
|
|
|
|
plugins := make([]*entity.PluginInfo, 0, len(searchResp.Data.Items))
|
|
for _, item := range searchResp.Data.Items {
|
|
plugin := convertSaasPluginItemToEntity(item)
|
|
plugins = append(plugins, plugin)
|
|
}
|
|
|
|
return plugins, searchResp.Data.HasMore, nil
|
|
}
|
|
|
|
func convertSaasPluginItemToEntity(item *domainDto.SaasPluginItem) *entity.PluginInfo {
|
|
if item == nil || item.MetaInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
metaInfo := item.MetaInfo
|
|
var pluginID int64
|
|
if id, err := strconv.ParseInt(metaInfo.EntityID, 10, 64); err == nil {
|
|
pluginID = id
|
|
}
|
|
|
|
// 创建插件清单
|
|
manifest := &model.PluginManifest{
|
|
SchemaVersion: "v1",
|
|
NameForModel: metaInfo.Name,
|
|
NameForHuman: metaInfo.Name,
|
|
DescriptionForModel: metaInfo.Description,
|
|
DescriptionForHuman: metaInfo.Description,
|
|
LogoURL: metaInfo.IconURL,
|
|
Auth: &model.AuthV2{
|
|
Type: func() consts.AuthzType {
|
|
if !item.PluginInfo.IsCallAvailable {
|
|
return consts.AuthTypeOfSaasInstalled
|
|
}
|
|
return consts.AuthzTypeOfNone
|
|
}(),
|
|
},
|
|
API: model.APIDesc{
|
|
Type: "openapi",
|
|
},
|
|
}
|
|
|
|
pluginInfo := &model.PluginInfo{
|
|
ID: pluginID,
|
|
PluginType: pluginCommon.PluginType_PLUGIN,
|
|
SpaceID: 0,
|
|
DeveloperID: 0,
|
|
APPID: nil,
|
|
IconURI: &metaInfo.IconURL,
|
|
ServerURL: ptr.Of(""),
|
|
CreatedAt: metaInfo.ListedAt,
|
|
UpdatedAt: metaInfo.ListedAt,
|
|
Manifest: manifest,
|
|
Source: ptr.Of(bot_common.PluginFrom_FromSaas),
|
|
SaasPluginExtra: &model.SaasPluginExtraInfo{
|
|
IsOfficial: metaInfo.IsOfficial,
|
|
JumpSaasURL: &metaInfo.ProductURL,
|
|
},
|
|
RefProductID: func() *int64 {
|
|
if id, err := strconv.ParseInt(metaInfo.ProductID, 10, 64); err == nil {
|
|
return ptr.Of(id)
|
|
}
|
|
return nil
|
|
}(),
|
|
}
|
|
|
|
return entity.NewPluginInfo(pluginInfo)
|
|
}
|
|
|
|
func convertCozePluginToEntity(cozePlugin *dto.SaasPluginToolsList) *entity.PluginInfo {
|
|
var pluginID int64
|
|
if id, err := strconv.ParseInt(cozePlugin.PluginID, 10, 64); err == nil {
|
|
pluginID = id
|
|
}
|
|
|
|
manifest := &model.PluginManifest{
|
|
SchemaVersion: "v1",
|
|
NameForModel: cozePlugin.Name,
|
|
NameForHuman: cozePlugin.Name,
|
|
DescriptionForModel: cozePlugin.Description,
|
|
DescriptionForHuman: cozePlugin.Description,
|
|
LogoURL: cozePlugin.IconURL,
|
|
Auth: &model.AuthV2{
|
|
Type: func() consts.AuthzType {
|
|
if !cozePlugin.IsCallAvailable {
|
|
return consts.AuthTypeOfSaasInstalled
|
|
}
|
|
return consts.AuthzTypeOfNone
|
|
}(),
|
|
},
|
|
API: model.APIDesc{
|
|
Type: "openapi",
|
|
},
|
|
}
|
|
|
|
pluginInfo := &model.PluginInfo{
|
|
ID: pluginID,
|
|
PluginType: pluginCommon.PluginType_PLUGIN,
|
|
SpaceID: 0,
|
|
DeveloperID: 0,
|
|
APPID: nil,
|
|
IconURL: &cozePlugin.IconURL,
|
|
ServerURL: ptr.Of("https://api.coze.cn"),
|
|
CreatedAt: cozePlugin.CreatedAt,
|
|
UpdatedAt: cozePlugin.UpdatedAt,
|
|
Manifest: manifest,
|
|
Source: ptr.Of(bot_common.PluginFrom_FromSaas),
|
|
}
|
|
|
|
return entity.NewPluginInfo(pluginInfo)
|
|
}
|
|
|
|
func (p *pluginServiceImpl) GetSaasPluginInfo(ctx context.Context, pluginIDs []int64) (plugin []*entity.PluginInfo, err error) {
|
|
client := saasapi.NewCozeAPIClient()
|
|
|
|
var idStrings []string
|
|
for _, id := range pluginIDs {
|
|
idStrings = append(idStrings, strconv.FormatInt(id, 10))
|
|
}
|
|
idsStr := strings.Join(idStrings, ",")
|
|
|
|
queryParams := map[string]interface{}{
|
|
"ids": idsStr,
|
|
}
|
|
|
|
resp, err := client.GetWithQuery(ctx, "/v1/plugins/mget", queryParams)
|
|
if err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginCallCozeAPIFailed)
|
|
}
|
|
|
|
var apiResp dto.SaasPluginToolsListResponse
|
|
|
|
if err := json.Unmarshal(resp.Data, &apiResp); err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginParseCozeAPIResponseFailed)
|
|
}
|
|
|
|
plugins := make([]*entity.PluginInfo, 0, len(idStrings))
|
|
for _, cozePlugin := range apiResp.Items {
|
|
plugin := convertCozePluginToEntity(&cozePlugin)
|
|
plugins = append(plugins, plugin)
|
|
}
|
|
|
|
return plugins, nil
|
|
}
|
|
|
|
func (p *pluginServiceImpl) searchSaasPlugin(ctx context.Context, req *domainDto.SearchSaasPluginRequest) (resp *domainDto.SearchSaasPluginResponse, err error) {
|
|
client := saasapi.NewCozeAPIClient()
|
|
|
|
// 构建查询参数
|
|
queryParams := make(map[string]any)
|
|
if req.Keyword != nil {
|
|
queryParams["keyword"] = req.Keyword
|
|
}
|
|
if req.PageNum != nil {
|
|
queryParams["page_num"] = req.PageNum
|
|
}
|
|
if req.PageSize != nil {
|
|
queryParams["page_size"] = req.PageSize
|
|
}
|
|
if req.SortType != nil {
|
|
queryParams["sort_type"] = req.SortType
|
|
}
|
|
if len(req.CategoryIDs) > 0 {
|
|
var categoryIDStrs []string
|
|
for _, id := range req.CategoryIDs {
|
|
categoryIDStrs = append(categoryIDStrs, strconv.FormatInt(id, 10))
|
|
}
|
|
queryParams["category_ids"] = strings.Join(categoryIDStrs, ",")
|
|
}
|
|
if req.IsOfficial != nil {
|
|
queryParams["is_official"] = req.IsOfficial
|
|
}
|
|
|
|
apiResp, err := client.GetWithQuery(ctx, "/v1/stores/plugins", queryParams)
|
|
if err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginCallCozeSearchAPIFailed)
|
|
}
|
|
|
|
var searchResp domainDto.SearchSaasPluginResponse
|
|
if err := json.Unmarshal(apiResp.Data, &searchResp.Data); err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginParseCozeSearchAPIResponseFailed)
|
|
}
|
|
|
|
return &searchResp, nil
|
|
}
|
|
|
|
func (p *pluginServiceImpl) ListSaasPluginCategories(ctx context.Context, req *domainDto.ListPluginCategoriesRequest) (resp *domainDto.ListPluginCategoriesResponse, err error) {
|
|
client := saasapi.NewCozeAPIClient()
|
|
|
|
queryParams := make(map[string]any)
|
|
if req.PageNum != nil {
|
|
queryParams["page_num"] = req.PageNum
|
|
}
|
|
if req.PageSize != nil {
|
|
queryParams["page_size"] = req.PageSize
|
|
}
|
|
if req.EntityType != nil {
|
|
queryParams["entity_type"] = req.EntityType
|
|
}
|
|
|
|
apiResp, err := client.GetWithQuery(ctx, "/v1/stores/categories", queryParams)
|
|
if err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginCallCozeCategoriesAPIFailed)
|
|
}
|
|
|
|
var categoriesResp domainDto.ListPluginCategoriesResponse
|
|
if err := json.Unmarshal(apiResp.Data, &categoriesResp.Data); err != nil {
|
|
return nil, errorx.WrapByCode(err, errno.ErrPluginParseCozeCategoriesAPIResponseFailed)
|
|
}
|
|
|
|
categoriesResp.Code = apiResp.Code
|
|
categoriesResp.Msg = apiResp.Msg
|
|
|
|
return &categoriesResp, nil
|
|
}
|