Files
zgene 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
first commit
2026-05-14 13:29:56 +08:00

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
}