feat(plugin): tool list
This commit is contained in:
@ -40,6 +40,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/application/search"
|
||||
"github.com/coze-dev/coze-studio/backend/application/singleagent"
|
||||
"github.com/coze-dev/coze-studio/backend/application/template"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
@ -54,6 +55,7 @@ func PublicGetProductList(ctx context.Context, c *app.RequestContext) {
|
||||
return
|
||||
}
|
||||
|
||||
req.EntityType = ptr.Of(product_common.ProductEntityType_SaasPlugin)
|
||||
var resp *product_public_api.GetProductListResponse
|
||||
switch req.GetEntityType() {
|
||||
case product_common.ProductEntityType_Plugin:
|
||||
|
||||
@ -327,54 +327,58 @@ func convertPluginToProductInfo(plugin *entity.PluginInfo) *productAPI.ProductIn
|
||||
IconURL: plugin.GetIconURI(),
|
||||
ListedAt: plugin.CreatedAt,
|
||||
},
|
||||
PluginExtra: &productAPI.PluginExtraInfo{
|
||||
IsOfficial: plugin.IsOfficial(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// convertPluginsToProductInfos converts a slice of plugins to ProductInfo slice
|
||||
func convertPluginsToProductInfos(plugins []*entity.PluginInfo) []*productAPI.ProductInfo {
|
||||
func (p *PluginApplicationService) convertPluginsToProductInfos(ctx context.Context, plugins []*entity.PluginInfo, tools map[int64][]*entity.ToolInfo) []*productAPI.ProductInfo {
|
||||
products := make([]*productAPI.ProductInfo, 0, len(plugins))
|
||||
for _, plugin := range plugins {
|
||||
products = append(products, convertPluginToProductInfo(plugin))
|
||||
var pExtra *productAPI.PluginExtraInfo
|
||||
if tool, exist := tools[plugin.ID]; exist {
|
||||
pluginExtra, err := p.buildPluginProductExtraInfo(ctx, plugin, tool)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "buildPluginProductExtraInfo failed: %v", err)
|
||||
} else {
|
||||
pExtra = pluginExtra
|
||||
}
|
||||
}
|
||||
pi := convertPluginToProductInfo(plugin)
|
||||
pi.PluginExtra = pExtra
|
||||
products = append(products, pi)
|
||||
}
|
||||
return products
|
||||
}
|
||||
|
||||
// getSaasPluginList is a common method to get SaaS plugin list from domain service
|
||||
func (t *PluginApplicationService) getSaasPluginList(ctx context.Context) ([]*entity.PluginInfo, int64, error) {
|
||||
domainReq := &dto.ListPluginProductsRequest{}
|
||||
domainResp, err := t.DomainSVC.ListSaasPluginProducts(ctx, domainReq)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return domainResp.Plugins, domainResp.Total, nil
|
||||
func (p *PluginApplicationService) getSaasPluginList(ctx context.Context, domainReq *dto.ListSaasPluginProductsRequest) (*dto.ListPluginProductsResponse, error) {
|
||||
return p.DomainSVC.ListSaasPluginProducts(ctx, domainReq)
|
||||
}
|
||||
|
||||
// convertPluginsToSuggestions converts plugins to suggestion products with deduplication and limit
|
||||
func convertPluginsToSuggestions(plugins []*entity.PluginInfo, limit int) []*productAPI.ProductInfo {
|
||||
func (p *PluginApplicationService) getSaasPluginToolsList(ctx context.Context, pluginIDs []int64) (map[int64][]*entity.ToolInfo, error) {
|
||||
return p.DomainSVC.BatchGetSaasPluginToolsInfo(ctx, pluginIDs)
|
||||
}
|
||||
|
||||
func convertPluginsToSuggestions(plugins []*entity.PluginInfo) []*productAPI.ProductInfo {
|
||||
suggestionProducts := make([]*productAPI.ProductInfo, 0, len(plugins))
|
||||
suggestionSet := make(map[string]bool) // Use map to avoid duplicates
|
||||
|
||||
for _, plugin := range plugins {
|
||||
// Add plugin as suggestion if name is unique
|
||||
|
||||
if plugin.GetName() != "" && !suggestionSet[plugin.GetName()] {
|
||||
suggestionProducts = append(suggestionProducts, convertPluginToProductInfo(plugin))
|
||||
suggestionSet[plugin.GetName()] = true
|
||||
}
|
||||
|
||||
// Limit suggestions to avoid too many results
|
||||
if len(suggestionProducts) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return suggestionProducts
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, req *productAPI.GetProductListRequest) (resp *productAPI.GetProductListResponse, err error) {
|
||||
plugins, total, err := t.getSaasPluginList(ctx)
|
||||
func (p *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, req *productAPI.GetProductListRequest) (resp *productAPI.GetProductListResponse, err error) {
|
||||
domainResp, err := p.getSaasPluginList(ctx, &dto.ListSaasPluginProductsRequest{
|
||||
PageNum: ptr.Of(req.PageNum),
|
||||
PageSize: ptr.Of(req.PageSize),
|
||||
Keyword: req.Keyword,
|
||||
EntityTypes: req.EntityTypes,
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts failed: %v", err)
|
||||
return &productAPI.GetProductListResponse{
|
||||
@ -383,21 +387,44 @@ func (t *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, re
|
||||
}, nil
|
||||
}
|
||||
|
||||
products := convertPluginsToProductInfos(plugins)
|
||||
// tools
|
||||
pluginIDs := make([]int64, 0, len(domainResp.Plugins))
|
||||
for _,product := range domainResp.Plugins{
|
||||
pluginIDs = append(pluginIDs, product.ID)
|
||||
}
|
||||
|
||||
tools, err := p.getSaasPluginToolsList(ctx, pluginIDs)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "BatchGetSaasPluginToolsInfo failed: %v", err)
|
||||
return &productAPI.GetProductListResponse{
|
||||
Code: -1,
|
||||
Message: "Failed to get SaaS plugin tools list",
|
||||
}, nil
|
||||
}
|
||||
|
||||
products := p.convertPluginsToProductInfos(ctx, domainResp.Plugins, tools)
|
||||
|
||||
return &productAPI.GetProductListResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.GetProductListData{
|
||||
Products: products,
|
||||
Total: int32(total),
|
||||
HasMore: false,
|
||||
Total: int32(domainResp.Total),
|
||||
HasMore: domainResp.HasMore,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) PublicSearchProduct(ctx context.Context, req *productAPI.SearchProductRequest) (resp *productAPI.SearchProductResponse, err error) {
|
||||
plugins, total, err := t.getSaasPluginList(ctx)
|
||||
func (p *PluginApplicationService) PublicSearchProduct(ctx context.Context, req *productAPI.SearchProductRequest) (resp *productAPI.SearchProductResponse, err error) {
|
||||
domainResp, err := p.getSaasPluginList(ctx, &dto.ListSaasPluginProductsRequest{
|
||||
PageNum: ptr.Of(req.PageNum),
|
||||
PageSize: ptr.Of(req.PageSize),
|
||||
Keyword: ptr.Of(req.Keyword),
|
||||
EntityTypes: req.EntityTypes,
|
||||
CategoryIDs: req.CategoryIDs,
|
||||
IsOfficial: req.IsOfficial,
|
||||
PluginType: req.PluginType,
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts failed: %v", err)
|
||||
return &productAPI.SearchProductResponse{
|
||||
@ -405,22 +432,42 @@ func (t *PluginApplicationService) PublicSearchProduct(ctx context.Context, req
|
||||
Message: "Failed to search SaaS plugins",
|
||||
}, nil
|
||||
}
|
||||
// tools
|
||||
pluginIDs := make([]int64, 0, len(domainResp.Plugins))
|
||||
for _,product := range domainResp.Plugins{
|
||||
pluginIDs = append(pluginIDs, product.ID)
|
||||
}
|
||||
|
||||
products := convertPluginsToProductInfos(plugins)
|
||||
tools, err := p.getSaasPluginToolsList(ctx, pluginIDs)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "BatchGetSaasPluginToolsInfo failed: %v", err)
|
||||
return &productAPI.SearchProductResponse{
|
||||
Code: -1,
|
||||
Message: "Failed to get SaaS plugin tools list",
|
||||
}, nil
|
||||
}
|
||||
|
||||
products := p.convertPluginsToProductInfos(ctx, domainResp.Plugins, tools)
|
||||
|
||||
return &productAPI.SearchProductResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.SearchProductResponseData{
|
||||
Products: products,
|
||||
Total: ptr.Of(int32(total)),
|
||||
HasMore: ptr.Of(false),
|
||||
Total: ptr.Of(int32(domainResp.Total)),
|
||||
HasMore: ptr.Of(domainResp.HasMore),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) PublicSearchSuggest(ctx context.Context, req *productAPI.SearchSuggestRequest) (resp *productAPI.SearchSuggestResponse, err error) {
|
||||
plugins, _, err := t.getSaasPluginList(ctx)
|
||||
func (p *PluginApplicationService) PublicSearchSuggest(ctx context.Context, req *productAPI.SearchSuggestRequest) (resp *productAPI.SearchSuggestResponse, err error) {
|
||||
domainResp, err := p.getSaasPluginList(ctx, &dto.ListSaasPluginProductsRequest{
|
||||
PageNum: req.PageNum,
|
||||
PageSize: req.PageSize,
|
||||
Keyword: req.Keyword,
|
||||
EntityTypes: req.EntityTypes,
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts for suggestions failed: %v", err)
|
||||
return &productAPI.SearchSuggestResponse{
|
||||
@ -429,30 +476,27 @@ func (t *PluginApplicationService) PublicSearchSuggest(ctx context.Context, req
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Convert plugins to suggestions with limit of 10
|
||||
suggestionProducts := convertPluginsToSuggestions(plugins, 10)
|
||||
suggestionProducts := convertPluginsToSuggestions(domainResp.Plugins)
|
||||
|
||||
return &productAPI.SearchSuggestResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.SearchSuggestResponseData{
|
||||
SuggestionV2: suggestionProducts,
|
||||
HasMore: ptr.Of(false),
|
||||
HasMore: ptr.Of(domainResp.HasMore),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) GetSaasProductCategoryList(ctx context.Context, req *productAPI.GetProductCategoryListRequest) (resp *productAPI.GetProductCategoryListResponse, err error) {
|
||||
// 构建 domain 层请求
|
||||
func (p *PluginApplicationService) GetSaasProductCategoryList(ctx context.Context, req *productAPI.GetProductCategoryListRequest) (resp *productAPI.GetProductCategoryListResponse, err error) {
|
||||
|
||||
domainReq := &dto.ListPluginCategoriesRequest{}
|
||||
|
||||
// 根据请求参数设置查询条件
|
||||
if req.GetEntityType() == productCommon.ProductEntityType_SaasPlugin {
|
||||
domainReq.EntityType = ptr.Of("plugin")
|
||||
}
|
||||
|
||||
// 调用 domain 层服务
|
||||
domainResp, err := t.DomainSVC.ListSaasPluginCategories(ctx, domainReq)
|
||||
domainResp, err := p.DomainSVC.ListSaasPluginCategories(ctx, domainReq)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginCategories failed: %v", err)
|
||||
return &productAPI.GetProductCategoryListResponse{
|
||||
@ -484,7 +528,7 @@ func (t *PluginApplicationService) GetSaasProductCategoryList(ctx context.Contex
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) GetProductCallInfo(ctx context.Context, req *productAPI.GetProductCallInfoRequest) (resp *productAPI.GetProductCallInfoResponse, err error) {
|
||||
func (p *PluginApplicationService) GetProductCallInfo(ctx context.Context, req *productAPI.GetProductCallInfoRequest) (resp *productAPI.GetProductCallInfoResponse, err error) {
|
||||
userID := ctxutil.GetUIDFromCtx(ctx)
|
||||
if userID == nil {
|
||||
return &productAPI.GetProductCallInfoResponse{
|
||||
@ -493,8 +537,7 @@ func (t *PluginApplicationService) GetProductCallInfo(ctx context.Context, req *
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Call GetSaasUserInfo
|
||||
_, err = t.userSVC.GetSaasUserInfo(ctx)
|
||||
_, err = p.userSVC.GetSaasUserInfo(ctx)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetSaasUserInfo failed: %v", err)
|
||||
return &productAPI.GetProductCallInfoResponse{
|
||||
@ -503,8 +546,7 @@ func (t *PluginApplicationService) GetProductCallInfo(ctx context.Context, req *
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Call GetUserBenefit
|
||||
benefit, err := t.userSVC.GetUserBenefit(ctx)
|
||||
benefit, err := p.userSVC.GetUserBenefit(ctx)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetUserBenefit failed: %v", err)
|
||||
return &productAPI.GetProductCallInfoResponse{
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
api "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
||||
)
|
||||
|
||||
@ -44,6 +45,9 @@ type PluginInfo struct {
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
|
||||
Source *bot_common.PluginSource
|
||||
Extra map[string]any
|
||||
|
||||
Manifest *PluginManifest
|
||||
OpenapiDoc *Openapi3T
|
||||
}
|
||||
|
||||
@ -44,6 +44,9 @@ type ToolInfo struct {
|
||||
ActivatedStatus *consts.ActivatedStatus
|
||||
DebugStatus *common.APIDebugStatus
|
||||
|
||||
Source *bot_common.PluginSource
|
||||
Extra map[string]any
|
||||
|
||||
Method *string
|
||||
SubURL *string
|
||||
Operation *Openapi3Operation
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// SearchSaasPluginRequest represents the request parameters for searching SaaS plugins
|
||||
type SearchSaasPluginRequest struct {
|
||||
Keyword *string `json:"keyword,omitempty"`
|
||||
@ -26,6 +30,8 @@ type SearchSaasPluginRequest struct {
|
||||
IsOfficial *bool `json:"is_official,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
// SearchSaasPluginResponse represents the response from coze.cn search API
|
||||
type SearchSaasPluginResponse struct {
|
||||
Code int `json:"code"`
|
||||
@ -129,3 +135,158 @@ type GetSaasPluginCallInfoResponse struct {
|
||||
|
||||
type GetSaasPluginCallInfoData struct {
|
||||
}
|
||||
|
||||
|
||||
type JsonSchemaType int32
|
||||
|
||||
const (
|
||||
JsonSchemaType_STRING JsonSchemaType = 1
|
||||
JsonSchemaType_NUMBER JsonSchemaType = 2
|
||||
JsonSchemaType_INTEGER JsonSchemaType = 3
|
||||
JsonSchemaType_BOOLEAN JsonSchemaType = 4
|
||||
JsonSchemaType_OBJECT JsonSchemaType = 5
|
||||
JsonSchemaType_ARRAY JsonSchemaType = 6
|
||||
JsonSchemaType_NULL JsonSchemaType = 7
|
||||
)
|
||||
|
||||
type AnyValue struct {
|
||||
Type JsonSchemaType `json:"type,omitempty"`
|
||||
StringValue string `json:"stringValue,omitempty"`
|
||||
}
|
||||
type JsonSchema struct {
|
||||
// core
|
||||
ID string `json:"$id,omitempty"`
|
||||
Schema string `json:"$schema,omitempty"`
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Comment string `json:"$comment,omitempty"`
|
||||
Defs map[string]*JsonSchema `json:"$defs,omitempty"`
|
||||
Definitions map[string]*JsonSchema `json:"definitions,omitempty"` // deprecated but still allowed
|
||||
|
||||
Anchor string `json:"$anchor,omitempty"`
|
||||
DynamicAnchor string `json:"$dynamicAnchor,omitempty"`
|
||||
DynamicRef string `json:"$dynamicRef,omitempty"`
|
||||
Vocabulary map[string]bool `json:"$vocabulary,omitempty"`
|
||||
|
||||
// metadata
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Default []byte `json:"default,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
WriteOnly bool `json:"writeOnly,omitempty"`
|
||||
|
||||
// validation
|
||||
// Use Type for a single type, or Types for multiple types; never both.
|
||||
Type JsonSchemaType `json:"type,omitempty"`
|
||||
Types []JsonSchemaType `json:"types,omitempty"`
|
||||
Enum []*AnyValue `json:"enum,omitempty"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty"`
|
||||
Minimum *float64 `json:"minimum,omitempty"`
|
||||
Maximum *float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMinimum *bool `json:"exclusiveMinimum,omitempty"`
|
||||
ExclusiveMaximum *bool `json:"exclusiveMaximum,omitempty"`
|
||||
MinLength *int32 `json:"minLength,omitempty"`
|
||||
MaxLength *int32 `json:"maxLength,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
|
||||
// arrays
|
||||
PrefixItems []*JsonSchema `json:"prefixItems,omitempty"`
|
||||
Items *JsonSchema `json:"items,omitempty"`
|
||||
MinItems *int32 `json:"minItems,omitempty"`
|
||||
MaxItems *int32 `json:"maxItems,omitempty"`
|
||||
AdditionalItems *JsonSchema `json:"additionalItems,omitempty"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty"`
|
||||
Contains *JsonSchema `json:"contains,omitempty"`
|
||||
MinContains *int32 `json:"minContains,omitempty"`
|
||||
MaxContains *int32 `json:"maxContains,omitempty"`
|
||||
UnevaluatedItems *JsonSchema `json:"unevaluatedItems,omitempty"`
|
||||
|
||||
// objects
|
||||
MinProperties *int32 `json:"minProperties,omitempty"`
|
||||
MaxProperties *int32 `json:"maxProperties,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
DependentRequired map[string][]string `json:"dependentRequired,omitempty"`
|
||||
Properties map[string]*JsonSchema `json:"properties,omitempty"`
|
||||
PatternProperties map[string]*JsonSchema `json:"patternProperties,omitempty"`
|
||||
AdditionalProperties *JsonSchema `json:"additionalProperties,omitempty"`
|
||||
PropertyNames *JsonSchema `json:"propertyNames,omitempty"`
|
||||
UnevaluatedProperties *JsonSchema `json:"unevaluatedProperties,omitempty"`
|
||||
|
||||
// logic
|
||||
AllOf []*JsonSchema `json:"allOf,omitempty"`
|
||||
AnyOf []*JsonSchema `json:"anyOf,omitempty"`
|
||||
OneOf []*JsonSchema `json:"oneOf,omitempty"`
|
||||
Not *JsonSchema `json:"not,omitempty"`
|
||||
|
||||
// conditional
|
||||
If map[string]*JsonSchema `json:"if,omitempty"`
|
||||
Then map[string]*JsonSchema `json:"then,omitempty"`
|
||||
Else map[string]*JsonSchema `json:"else,omitempty"`
|
||||
DependentSchemas map[string]*JsonSchema `json:"dependentSchemas,omitempty"`
|
||||
|
||||
// other
|
||||
ContentEncoding *JsonSchema `json:"contentEncoding,omitempty"`
|
||||
ContentMediaType *JsonSchema `json:"contentMediaType,omitempty"`
|
||||
ContentSchema *JsonSchema `json:"contentSchema,omitempty"`
|
||||
|
||||
Format string `json:"format,omitempty"`
|
||||
|
||||
// Extra allows for additional keywords beyond those specified.
|
||||
Extra map[string]*JsonSchema `json:"-"`
|
||||
}
|
||||
|
||||
// stringToJsonSchemaType converts a string to JsonSchemaType
|
||||
func stringToJsonSchemaType(s string) JsonSchemaType {
|
||||
switch s {
|
||||
case "string":
|
||||
return JsonSchemaType_STRING
|
||||
case "number":
|
||||
return JsonSchemaType_NUMBER
|
||||
case "integer":
|
||||
return JsonSchemaType_INTEGER
|
||||
case "boolean":
|
||||
return JsonSchemaType_BOOLEAN
|
||||
case "object":
|
||||
return JsonSchemaType_OBJECT
|
||||
case "array":
|
||||
return JsonSchemaType_ARRAY
|
||||
case "null":
|
||||
return JsonSchemaType_NULL
|
||||
default:
|
||||
return JsonSchemaType_STRING // default fallback
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements custom JSON unmarshaling for JsonSchema
|
||||
func (js *JsonSchema) UnmarshalJSON(data []byte) error {
|
||||
// Create a temporary struct with the same fields but string type for Type field
|
||||
type Alias JsonSchema
|
||||
aux := &struct {
|
||||
TypeString interface{} `json:"type"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(js),
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle the type field conversion
|
||||
if aux.TypeString != nil {
|
||||
switch v := aux.TypeString.(type) {
|
||||
case string:
|
||||
js.Type = stringToJsonSchemaType(v)
|
||||
case []interface{}:
|
||||
// Handle array of types
|
||||
js.Types = make([]JsonSchemaType, len(v))
|
||||
for i, typeVal := range v {
|
||||
if typeStr, ok := typeVal.(string); ok {
|
||||
js.Types[i] = stringToJsonSchemaType(typeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/marketplace/product_common"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
@ -94,11 +95,23 @@ type CreateDraftPluginWithCodeResponse struct {
|
||||
Tools []*entity.ToolInfo
|
||||
}
|
||||
|
||||
type ListPluginProductsRequest struct{}
|
||||
type ListPluginProductsRequest struct {
|
||||
}
|
||||
|
||||
type ListSaasPluginProductsRequest struct {
|
||||
PageNum *int32 `json:"page_num,omitempty"`
|
||||
PageSize *int32 `json:"page_size,omitempty"`
|
||||
Keyword *string `json:"keyword,omitempty"`
|
||||
EntityTypes []product_common.ProductEntityType `json:"entity_types,omitempty"`
|
||||
CategoryIDs []int64 `json:"category_ids,omitempty"`
|
||||
IsOfficial *bool `json:"is_official,omitempty"`
|
||||
PluginType *product_common.PluginType `json:"plugin_type,omitempty"`
|
||||
}
|
||||
|
||||
type ListPluginProductsResponse struct {
|
||||
Plugins []*entity.PluginInfo
|
||||
Total int64
|
||||
HasMore bool
|
||||
}
|
||||
|
||||
type CopyPluginRequest struct {
|
||||
@ -113,3 +126,51 @@ type CopyPluginResponse struct {
|
||||
Plugin *entity.PluginInfo
|
||||
Tools map[int64]*entity.ToolInfo // old tool id -> new tool
|
||||
}
|
||||
|
||||
// DefaultParamSource 表示默认参数的设置来源
|
||||
type DefaultParamSource int32
|
||||
|
||||
const (
|
||||
DefaultParamSource_Input DefaultParamSource = 0 // 默认用户输入
|
||||
DefaultParamSource_Variable DefaultParamSource = 1 // 引用变量
|
||||
)
|
||||
|
||||
// AssistParameterType 表示辅助参数类型
|
||||
type AssistParameterType int32
|
||||
|
||||
const (
|
||||
AssistParameterType_DEFAULT AssistParameterType = 1
|
||||
AssistParameterType_IMAGE AssistParameterType = 2
|
||||
AssistParameterType_DOC AssistParameterType = 3
|
||||
AssistParameterType_CODE AssistParameterType = 4
|
||||
AssistParameterType_PPT AssistParameterType = 5
|
||||
AssistParameterType_TXT AssistParameterType = 6
|
||||
AssistParameterType_EXCEL AssistParameterType = 7
|
||||
AssistParameterType_AUDIO AssistParameterType = 8
|
||||
AssistParameterType_ZIP AssistParameterType = 9
|
||||
AssistParameterType_VIDEO AssistParameterType = 10
|
||||
)
|
||||
|
||||
type Parameter struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Desc string `json:"desc,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
SubParameters []*Parameter `json:"subParameters,omitempty"`
|
||||
SubType string `json:"subType,omitempty"` // 如果Type是数组,则有subtype
|
||||
FromNodeId *string `json:"fromNodeId,omitempty"` // 如果入参的值是引用的则有fromNodeId
|
||||
FromOutput []string `json:"fromOutput,omitempty"` // 具体引用哪个节点的key
|
||||
Value *string `json:"value,omitempty"` // 如果入参是用户手输 就放这里
|
||||
Format *string `json:"format,omitempty"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
EnumList []string `json:"enumList,omitempty"`
|
||||
EnumVarNames []string `json:"enumVarNames,omitempty"`
|
||||
Minimum *float64 `json:"minimum,omitempty"`
|
||||
Maximum *float64 `json:"maximum,omitempty"`
|
||||
ExclusiveMinimum *bool `json:"exclusiveMinimum,omitempty"`
|
||||
ExclusiveMaximum *bool `json:"exclusiveMaximum,omitempty"`
|
||||
BizExtend *string `json:"bizExtend,omitempty"`
|
||||
DefaultParamSource *DefaultParamSource `json:"defaultParamSource,omitempty"` // 默认入参的设置来源
|
||||
VariableRef *string `json:"variableRef,omitempty"` // 引用variable的key
|
||||
AssistType *AssistParameterType `json:"assistType,omitempty"`
|
||||
}
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -10,19 +26,21 @@ const TableNamePlugin = "plugin"
|
||||
|
||||
// Plugin Latest Plugin
|
||||
type Plugin struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:Plugin ID" json:"id"` // Plugin ID
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space ID
|
||||
DeveloperID int64 `gorm:"column:developer_id;not null;comment:Developer ID" json:"developer_id"` // Developer ID
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:Application ID" json:"app_id"` // Application ID
|
||||
IconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URI
|
||||
ServerURL string `gorm:"column:server_url;not null;comment:Server URL" json:"server_url"` // Server URL
|
||||
PluginType int32 `gorm:"column:plugin_type;not null;comment:Plugin Type, 1:http, 6:local" json:"plugin_type"` // Plugin Type, 1:http, 6:local
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds
|
||||
Version string `gorm:"column:version;not null;comment:Plugin Version, e.g. v1.0.0" json:"version"` // Plugin Version, e.g. v1.0.0
|
||||
VersionDesc string `gorm:"column:version_desc;comment:Plugin Version Description" json:"version_desc"` // Plugin Version Description
|
||||
Manifest *model.PluginManifest `gorm:"column:manifest;comment:Plugin Manifest;serializer:json" json:"manifest"` // Plugin Manifest
|
||||
OpenapiDoc *model.Openapi3T `gorm:"column:openapi_doc;comment:OpenAPI Document, only stores the root;serializer:json" json:"openapi_doc"` // OpenAPI Document, only stores the root
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:Plugin ID" json:"id"` // Plugin ID
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space ID
|
||||
DeveloperID int64 `gorm:"column:developer_id;not null;comment:Developer ID" json:"developer_id"` // Developer ID
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:Application ID" json:"app_id"` // Application ID
|
||||
IconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URI
|
||||
ServerURL string `gorm:"column:server_url;not null;comment:Server URL" json:"server_url"` // Server URL
|
||||
PluginType int32 `gorm:"column:plugin_type;not null;comment:Plugin Type, 1:http, 6:local" json:"plugin_type"` // Plugin Type, 1:http, 6:local
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds
|
||||
Version string `gorm:"column:version;not null;comment:Plugin Version, e.g. v1.0.0" json:"version"` // Plugin Version, e.g. v1.0.0
|
||||
VersionDesc string `gorm:"column:version_desc;comment:Plugin Version Description" json:"version_desc"` // Plugin Version Description
|
||||
Manifest *model.PluginManifest `gorm:"column:manifest;comment:Plugin Manifest;serializer:json" json:"manifest"` // Plugin Manifest
|
||||
OpenapiDoc *model.Openapi3T `gorm:"column:openapi_doc;comment:OpenAPI Document, only stores the root;serializer:json" json:"openapi_doc"` // OpenAPI Document, only stores the root
|
||||
Source int32 `gorm:"column:source;not null;comment:plugin source 1 from saas 0 default" json:"source"` // plugin source 1 from saas 0 default
|
||||
Ext map[string]interface{} `gorm:"column:ext;comment:extra ;serializer:json" json:"ext"` // extra
|
||||
}
|
||||
|
||||
// TableName Plugin's table name
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -13,20 +29,22 @@ const TableNamePluginVersion = "plugin_version"
|
||||
|
||||
// PluginVersion Plugin Version
|
||||
type PluginVersion struct {
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:Primary Key ID" json:"id"` // Primary Key ID
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space ID
|
||||
DeveloperID int64 `gorm:"column:developer_id;not null;comment:Developer ID" json:"developer_id"` // Developer ID
|
||||
PluginID int64 `gorm:"column:plugin_id;not null;comment:Plugin ID" json:"plugin_id"` // Plugin ID
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:Application ID" json:"app_id"` // Application ID
|
||||
IconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URI
|
||||
ServerURL string `gorm:"column:server_url;not null;comment:Server URL" json:"server_url"` // Server URL
|
||||
PluginType int32 `gorm:"column:plugin_type;not null;comment:Plugin Type, 1:http, 6:local" json:"plugin_type"` // Plugin Type, 1:http, 6:local
|
||||
Version string `gorm:"column:version;not null;comment:Plugin Version, e.g. v1.0.0" json:"version"` // Plugin Version, e.g. v1.0.0
|
||||
VersionDesc string `gorm:"column:version_desc;comment:Plugin Version Description" json:"version_desc"` // Plugin Version Description
|
||||
Manifest *model.PluginManifest `gorm:"column:manifest;comment:Plugin Manifest;serializer:json" json:"manifest"` // Plugin Manifest
|
||||
OpenapiDoc *model.Openapi3T `gorm:"column:openapi_doc;comment:OpenAPI Document, only stores the root;serializer:json" json:"openapi_doc"` // OpenAPI Document, only stores the root
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time
|
||||
ID int64 `gorm:"column:id;primaryKey;comment:Primary Key ID" json:"id"` // Primary Key ID
|
||||
SpaceID int64 `gorm:"column:space_id;not null;comment:Space ID" json:"space_id"` // Space ID
|
||||
DeveloperID int64 `gorm:"column:developer_id;not null;comment:Developer ID" json:"developer_id"` // Developer ID
|
||||
PluginID int64 `gorm:"column:plugin_id;not null;comment:Plugin ID" json:"plugin_id"` // Plugin ID
|
||||
AppID int64 `gorm:"column:app_id;not null;comment:Application ID" json:"app_id"` // Application ID
|
||||
IconURI string `gorm:"column:icon_uri;not null;comment:Icon URI" json:"icon_uri"` // Icon URI
|
||||
ServerURL string `gorm:"column:server_url;not null;comment:Server URL" json:"server_url"` // Server URL
|
||||
PluginType int32 `gorm:"column:plugin_type;not null;comment:Plugin Type, 1:http, 6:local" json:"plugin_type"` // Plugin Type, 1:http, 6:local
|
||||
Version string `gorm:"column:version;not null;comment:Plugin Version, e.g. v1.0.0" json:"version"` // Plugin Version, e.g. v1.0.0
|
||||
VersionDesc string `gorm:"column:version_desc;comment:Plugin Version Description" json:"version_desc"` // Plugin Version Description
|
||||
Manifest *model.PluginManifest `gorm:"column:manifest;comment:Plugin Manifest;serializer:json" json:"manifest"` // Plugin Manifest
|
||||
OpenapiDoc *model.Openapi3T `gorm:"column:openapi_doc;comment:OpenAPI Document, only stores the root;serializer:json" json:"openapi_doc"` // OpenAPI Document, only stores the root
|
||||
Source int32 `gorm:"column:source;not null;comment:plugin source 1 from saas 0 default" json:"source"` // plugin source 1 from saas 0 default
|
||||
Ext map[string]interface{} `gorm:"column:ext;comment:extra ;serializer:json" json:"ext"` // extra
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time
|
||||
}
|
||||
|
||||
// TableName PluginVersion's table name
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -19,6 +35,8 @@ type Tool struct {
|
||||
Method string `gorm:"column:method;not null;comment:HTTP Request Method" json:"method"` // HTTP Request Method
|
||||
Operation *model.Openapi3Operation `gorm:"column:operation;comment:Tool Openapi Operation Schema;serializer:json" json:"operation"` // Tool Openapi Operation Schema
|
||||
ActivatedStatus int32 `gorm:"column:activated_status;not null;comment:0:activated; 1:deactivated" json:"activated_status"` // 0:activated; 1:deactivated
|
||||
Source int32 `gorm:"column:source;not null;comment:tool source 1 coze saas 0 default" json:"source"` // tool source 1 coze saas 0 default
|
||||
Ext map[string]interface{} `gorm:"column:ext;comment:extra;serializer:json" json:"ext"` // extra
|
||||
}
|
||||
|
||||
// TableName Tool's table name
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -22,6 +38,8 @@ type ToolVersion struct {
|
||||
Operation *model.Openapi3Operation `gorm:"column:operation;comment:Tool Openapi Operation Schema;serializer:json" json:"operation"` // Tool Openapi Operation Schema
|
||||
CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time
|
||||
Source int32 `gorm:"column:source;not null;comment:tool source 1 coze saas 0 default" json:"source"` // tool source 1 coze saas 0 default
|
||||
Ext map[string]interface{} `gorm:"column:ext;comment:extra;serializer:json" json:"ext"` // extra
|
||||
}
|
||||
|
||||
// TableName ToolVersion's table name
|
||||
|
||||
@ -25,6 +25,7 @@ import (
|
||||
"gorm.io/gen/field"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
plugin_develop_common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
||||
pluginModel "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
|
||||
@ -63,6 +64,8 @@ func (p pluginPO) ToDO() *entity.PluginInfo {
|
||||
VersionDesc: &p.VersionDesc,
|
||||
Manifest: p.Manifest,
|
||||
OpenapiDoc: p.OpenapiDoc,
|
||||
Source: bot_common.PluginSourcePtr(bot_common.PluginSource(p.Source)),
|
||||
Extra: p.Ext,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"gorm.io/gen/field"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
plugin_develop_common "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
||||
pluginModel "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
|
||||
@ -62,6 +63,8 @@ func (p pluginVersionPO) ToDO() *entity.PluginInfo {
|
||||
VersionDesc: &p.VersionDesc,
|
||||
Manifest: p.Manifest,
|
||||
OpenapiDoc: p.OpenapiDoc,
|
||||
Source: bot_common.PluginSourcePtr(bot_common.PluginSource(p.Source)),
|
||||
Extra: p.Ext,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -40,6 +56,8 @@ func newPlugin(db *gorm.DB, opts ...gen.DOOption) plugin {
|
||||
_plugin.VersionDesc = field.NewString(tableName, "version_desc")
|
||||
_plugin.Manifest = field.NewField(tableName, "manifest")
|
||||
_plugin.OpenapiDoc = field.NewField(tableName, "openapi_doc")
|
||||
_plugin.Source = field.NewInt32(tableName, "source")
|
||||
_plugin.Ext = field.NewField(tableName, "ext")
|
||||
|
||||
_plugin.fillFieldMap()
|
||||
|
||||
@ -64,6 +82,8 @@ type plugin struct {
|
||||
VersionDesc field.String // Plugin Version Description
|
||||
Manifest field.Field // Plugin Manifest
|
||||
OpenapiDoc field.Field // OpenAPI Document, only stores the root
|
||||
Source field.Int32 // plugin source 1 from saas 0 default
|
||||
Ext field.Field // extra
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@ -93,6 +113,8 @@ func (p *plugin) updateTableName(table string) *plugin {
|
||||
p.VersionDesc = field.NewString(table, "version_desc")
|
||||
p.Manifest = field.NewField(table, "manifest")
|
||||
p.OpenapiDoc = field.NewField(table, "openapi_doc")
|
||||
p.Source = field.NewInt32(table, "source")
|
||||
p.Ext = field.NewField(table, "ext")
|
||||
|
||||
p.fillFieldMap()
|
||||
|
||||
@ -109,7 +131,7 @@ func (p *plugin) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
func (p *plugin) fillFieldMap() {
|
||||
p.fieldMap = make(map[string]field.Expr, 13)
|
||||
p.fieldMap = make(map[string]field.Expr, 15)
|
||||
p.fieldMap["id"] = p.ID
|
||||
p.fieldMap["space_id"] = p.SpaceID
|
||||
p.fieldMap["developer_id"] = p.DeveloperID
|
||||
@ -123,6 +145,8 @@ func (p *plugin) fillFieldMap() {
|
||||
p.fieldMap["version_desc"] = p.VersionDesc
|
||||
p.fieldMap["manifest"] = p.Manifest
|
||||
p.fieldMap["openapi_doc"] = p.OpenapiDoc
|
||||
p.fieldMap["source"] = p.Source
|
||||
p.fieldMap["ext"] = p.Ext
|
||||
}
|
||||
|
||||
func (p plugin) clone(db *gorm.DB) plugin {
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -39,6 +55,8 @@ func newPluginVersion(db *gorm.DB, opts ...gen.DOOption) pluginVersion {
|
||||
_pluginVersion.VersionDesc = field.NewString(tableName, "version_desc")
|
||||
_pluginVersion.Manifest = field.NewField(tableName, "manifest")
|
||||
_pluginVersion.OpenapiDoc = field.NewField(tableName, "openapi_doc")
|
||||
_pluginVersion.Source = field.NewInt32(tableName, "source")
|
||||
_pluginVersion.Ext = field.NewField(tableName, "ext")
|
||||
_pluginVersion.CreatedAt = field.NewInt64(tableName, "created_at")
|
||||
_pluginVersion.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
|
||||
@ -64,6 +82,8 @@ type pluginVersion struct {
|
||||
VersionDesc field.String // Plugin Version Description
|
||||
Manifest field.Field // Plugin Manifest
|
||||
OpenapiDoc field.Field // OpenAPI Document, only stores the root
|
||||
Source field.Int32 // plugin source 1 from saas 0 default
|
||||
Ext field.Field // extra
|
||||
CreatedAt field.Int64 // Create Time in Milliseconds
|
||||
DeletedAt field.Field // Delete Time
|
||||
|
||||
@ -94,6 +114,8 @@ func (p *pluginVersion) updateTableName(table string) *pluginVersion {
|
||||
p.VersionDesc = field.NewString(table, "version_desc")
|
||||
p.Manifest = field.NewField(table, "manifest")
|
||||
p.OpenapiDoc = field.NewField(table, "openapi_doc")
|
||||
p.Source = field.NewInt32(table, "source")
|
||||
p.Ext = field.NewField(table, "ext")
|
||||
p.CreatedAt = field.NewInt64(table, "created_at")
|
||||
p.DeletedAt = field.NewField(table, "deleted_at")
|
||||
|
||||
@ -112,7 +134,7 @@ func (p *pluginVersion) GetFieldByName(fieldName string) (field.OrderExpr, bool)
|
||||
}
|
||||
|
||||
func (p *pluginVersion) fillFieldMap() {
|
||||
p.fieldMap = make(map[string]field.Expr, 14)
|
||||
p.fieldMap = make(map[string]field.Expr, 16)
|
||||
p.fieldMap["id"] = p.ID
|
||||
p.fieldMap["space_id"] = p.SpaceID
|
||||
p.fieldMap["developer_id"] = p.DeveloperID
|
||||
@ -125,6 +147,8 @@ func (p *pluginVersion) fillFieldMap() {
|
||||
p.fieldMap["version_desc"] = p.VersionDesc
|
||||
p.fieldMap["manifest"] = p.Manifest
|
||||
p.fieldMap["openapi_doc"] = p.OpenapiDoc
|
||||
p.fieldMap["source"] = p.Source
|
||||
p.fieldMap["ext"] = p.Ext
|
||||
p.fieldMap["created_at"] = p.CreatedAt
|
||||
p.fieldMap["deleted_at"] = p.DeletedAt
|
||||
}
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -36,6 +52,8 @@ func newTool(db *gorm.DB, opts ...gen.DOOption) tool {
|
||||
_tool.Method = field.NewString(tableName, "method")
|
||||
_tool.Operation = field.NewField(tableName, "operation")
|
||||
_tool.ActivatedStatus = field.NewInt32(tableName, "activated_status")
|
||||
_tool.Source = field.NewInt32(tableName, "source")
|
||||
_tool.Ext = field.NewField(tableName, "ext")
|
||||
|
||||
_tool.fillFieldMap()
|
||||
|
||||
@ -56,6 +74,8 @@ type tool struct {
|
||||
Method field.String // HTTP Request Method
|
||||
Operation field.Field // Tool Openapi Operation Schema
|
||||
ActivatedStatus field.Int32 // 0:activated; 1:deactivated
|
||||
Source field.Int32 // tool source 1 coze saas 0 default
|
||||
Ext field.Field // extra
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@ -81,6 +101,8 @@ func (t *tool) updateTableName(table string) *tool {
|
||||
t.Method = field.NewString(table, "method")
|
||||
t.Operation = field.NewField(table, "operation")
|
||||
t.ActivatedStatus = field.NewInt32(table, "activated_status")
|
||||
t.Source = field.NewInt32(table, "source")
|
||||
t.Ext = field.NewField(table, "ext")
|
||||
|
||||
t.fillFieldMap()
|
||||
|
||||
@ -97,7 +119,7 @@ func (t *tool) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
func (t *tool) fillFieldMap() {
|
||||
t.fieldMap = make(map[string]field.Expr, 9)
|
||||
t.fieldMap = make(map[string]field.Expr, 11)
|
||||
t.fieldMap["id"] = t.ID
|
||||
t.fieldMap["plugin_id"] = t.PluginID
|
||||
t.fieldMap["created_at"] = t.CreatedAt
|
||||
@ -107,6 +129,8 @@ func (t *tool) fillFieldMap() {
|
||||
t.fieldMap["method"] = t.Method
|
||||
t.fieldMap["operation"] = t.Operation
|
||||
t.fieldMap["activated_status"] = t.ActivatedStatus
|
||||
t.fieldMap["source"] = t.Source
|
||||
t.fieldMap["ext"] = t.Ext
|
||||
}
|
||||
|
||||
func (t tool) clone(db *gorm.DB) tool {
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
@ -36,6 +52,8 @@ func newToolVersion(db *gorm.DB, opts ...gen.DOOption) toolVersion {
|
||||
_toolVersion.Operation = field.NewField(tableName, "operation")
|
||||
_toolVersion.CreatedAt = field.NewInt64(tableName, "created_at")
|
||||
_toolVersion.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
_toolVersion.Source = field.NewInt32(tableName, "source")
|
||||
_toolVersion.Ext = field.NewField(tableName, "ext")
|
||||
|
||||
_toolVersion.fillFieldMap()
|
||||
|
||||
@ -56,6 +74,8 @@ type toolVersion struct {
|
||||
Operation field.Field // Tool Openapi Operation Schema
|
||||
CreatedAt field.Int64 // Create Time in Milliseconds
|
||||
DeletedAt field.Field // Delete Time
|
||||
Source field.Int32 // tool source 1 coze saas 0 default
|
||||
Ext field.Field // extra
|
||||
|
||||
fieldMap map[string]field.Expr
|
||||
}
|
||||
@ -81,6 +101,8 @@ func (t *toolVersion) updateTableName(table string) *toolVersion {
|
||||
t.Operation = field.NewField(table, "operation")
|
||||
t.CreatedAt = field.NewInt64(table, "created_at")
|
||||
t.DeletedAt = field.NewField(table, "deleted_at")
|
||||
t.Source = field.NewInt32(table, "source")
|
||||
t.Ext = field.NewField(table, "ext")
|
||||
|
||||
t.fillFieldMap()
|
||||
|
||||
@ -97,7 +119,7 @@ func (t *toolVersion) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||
}
|
||||
|
||||
func (t *toolVersion) fillFieldMap() {
|
||||
t.fieldMap = make(map[string]field.Expr, 9)
|
||||
t.fieldMap = make(map[string]field.Expr, 11)
|
||||
t.fieldMap["id"] = t.ID
|
||||
t.fieldMap["tool_id"] = t.ToolID
|
||||
t.fieldMap["plugin_id"] = t.PluginID
|
||||
@ -107,6 +129,8 @@ func (t *toolVersion) fillFieldMap() {
|
||||
t.fieldMap["operation"] = t.Operation
|
||||
t.fieldMap["created_at"] = t.CreatedAt
|
||||
t.fieldMap["deleted_at"] = t.DeletedAt
|
||||
t.fieldMap["source"] = t.Source
|
||||
t.fieldMap["ext"] = t.Ext
|
||||
}
|
||||
|
||||
func (t toolVersion) clone(db *gorm.DB) toolVersion {
|
||||
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"gorm.io/gen/field"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/internal/dal/model"
|
||||
@ -58,6 +59,9 @@ func (t toolPO) ToDO() *entity.ToolInfo {
|
||||
Method: ptr.Of(t.Method),
|
||||
Operation: t.Operation,
|
||||
ActivatedStatus: ptr.Of(consts.ActivatedStatus(t.ActivatedStatus)),
|
||||
|
||||
Source: bot_common.PluginSourcePtr(bot_common.PluginSource(t.Source)),
|
||||
Extra: t.Ext,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
pluginModel "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/internal/dal/model"
|
||||
@ -54,6 +55,9 @@ func (t toolVersionPO) ToDO() *entity.ToolInfo {
|
||||
SubURL: &t.SubURL,
|
||||
Method: ptr.Of(t.Method),
|
||||
Operation: t.Operation,
|
||||
|
||||
Source: bot_common.PluginSourcePtr(bot_common.PluginSource(t.Source)),
|
||||
Extra: t.Ext,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +97,19 @@ func (p *pluginServiceImpl) ListPluginProducts(ctx context.Context, req *dto.Lis
|
||||
return plugins[i].GetRefProductID() < plugins[j].GetRefProductID()
|
||||
})
|
||||
|
||||
// official plugins
|
||||
officialPlugins, _, err := p.pluginRepo.ListCustomOnlinePlugins(ctx, 999999, dto.PageInfo{
|
||||
Page: 1,
|
||||
Size: 1000,
|
||||
OrderByACS: ptr.Of(true),
|
||||
SortBy:ptr.Of(dto.SortByCreatedAt),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "ListCustomOnlinePlugins failed, spaceID=999999")
|
||||
}
|
||||
|
||||
plugins = append(plugins, officialPlugins...)
|
||||
|
||||
return &dto.ListPluginProductsResponse{
|
||||
Plugins: plugins,
|
||||
Total: int64(len(plugins)),
|
||||
|
||||
@ -21,9 +21,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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/contract/plugin/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/convert/api"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
domainDto "github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/entity"
|
||||
@ -43,9 +47,14 @@ type CozePlugin struct {
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (p *pluginServiceImpl) ListSaasPluginProducts(ctx context.Context, req *domainDto.ListPluginProductsRequest) (resp *domainDto.ListPluginProductsResponse, err error) {
|
||||
|
||||
plugins, err := p.fetchSaasPluginsFromCoze(ctx)
|
||||
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,
|
||||
}
|
||||
plugins, hasMore,err := p.fetchSaasPluginsFromCoze(ctx, searchReq)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "fetchSaasPluginsFromCoze failed")
|
||||
}
|
||||
@ -53,14 +62,166 @@ func (p *pluginServiceImpl) ListSaasPluginProducts(ctx context.Context, req *dom
|
||||
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, err error) {
|
||||
if len(pluginIDs) == 0 {
|
||||
return make(map[int64][]*entity.ToolInfo), nil
|
||||
}
|
||||
|
||||
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.Wrapf(err, "failed to call coze.cn /v1/plugins/mget API")
|
||||
}
|
||||
|
||||
var apiResp struct {
|
||||
Plugins []struct {
|
||||
Tools []struct {
|
||||
ToolID string `json:"tool_id"`
|
||||
Description string `json:"description"`
|
||||
InputSchema *domainDto.JsonSchema `json:"inputSchema"`
|
||||
Name string `json:"name"`
|
||||
OutputSchema *domainDto.JsonSchema `json:"outputSchema"`
|
||||
} `json:"tools"`
|
||||
McpJSON string `json:"mcp_json"`
|
||||
} `json:"plugins"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Data, &apiResp); err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to parse coze.cn API response")
|
||||
}
|
||||
|
||||
result := make(map[int64][]*entity.ToolInfo)
|
||||
|
||||
for i, plugin := range apiResp.Plugins {
|
||||
if i >= len(pluginIDs) {
|
||||
break
|
||||
}
|
||||
pluginID := pluginIDs[i]
|
||||
toolInfos := make([]*entity.ToolInfo, 0, len(plugin.Tools))
|
||||
|
||||
for _, tool := range plugin.Tools {
|
||||
|
||||
openapi3Operation, err := api.APIParamsToOpenapiOperation(convertFromJsonSchema(tool.InputSchema), convertFromJsonSchema(tool.OutputSchema))
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to convert input schema to openapi operation parameters")
|
||||
}
|
||||
openapi3Operation.OperationID = tool.Name
|
||||
openapi3Operation.Summary = tool.Description
|
||||
operation := &model.Openapi3Operation{
|
||||
Operation: openapi3Operation,
|
||||
}
|
||||
id, err := strconv.ParseInt(tool.ToolID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to parse tool ID %s", tool.ToolID)
|
||||
}
|
||||
toolInfo := &entity.ToolInfo{
|
||||
ID: id,
|
||||
PluginID: pluginID,
|
||||
Operation: operation,
|
||||
Source: ptr.Of(bot_common.PluginSource_FromSaas),
|
||||
}
|
||||
|
||||
toolInfos = append(toolInfos, toolInfo)
|
||||
}
|
||||
|
||||
result[pluginID] = toolInfos
|
||||
}
|
||||
|
||||
logs.CtxInfof(ctx, "fetched SaaS plugin tools info from coze.cn, pluginIDs: %v, total tools: %d", pluginIDs, len(result))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func mapJsonSchemaTypeToParameterType(schemaType domainDto.JsonSchemaType) pluginCommon.ParameterType {
|
||||
switch schemaType {
|
||||
case domainDto.JsonSchemaType_STRING:
|
||||
return pluginCommon.ParameterType_String
|
||||
case domainDto.JsonSchemaType_NUMBER:
|
||||
return pluginCommon.ParameterType_Number
|
||||
case domainDto.JsonSchemaType_INTEGER:
|
||||
return pluginCommon.ParameterType_Integer
|
||||
case domainDto.JsonSchemaType_BOOLEAN:
|
||||
return pluginCommon.ParameterType_Bool
|
||||
case domainDto.JsonSchemaType_OBJECT:
|
||||
return pluginCommon.ParameterType_Object
|
||||
case domainDto.JsonSchemaType_ARRAY:
|
||||
return pluginCommon.ParameterType_Array
|
||||
default:
|
||||
return pluginCommon.ParameterType_String
|
||||
}
|
||||
}
|
||||
|
||||
func convertFromJsonSchema(schema *domainDto.JsonSchema) []*pluginCommon.APIParameter {
|
||||
if schema == nil {
|
||||
return []*pluginCommon.APIParameter{}
|
||||
}
|
||||
|
||||
var parameters []*pluginCommon.APIParameter
|
||||
|
||||
// Handle object type with properties
|
||||
if schema.Type == domainDto.JsonSchemaType_OBJECT && len(schema.Properties) > 0 {
|
||||
// Create a set of required fields for quick lookup
|
||||
requiredFields := make(map[string]bool)
|
||||
for _, field := range schema.Required {
|
||||
requiredFields[field] = true
|
||||
}
|
||||
|
||||
// Convert each property to a parameter
|
||||
for name, propSchema := range schema.Properties {
|
||||
if propSchema == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
param := &pluginCommon.APIParameter{
|
||||
Name: name,
|
||||
Desc: propSchema.Description,
|
||||
IsRequired: requiredFields[name],
|
||||
Type: mapJsonSchemaTypeToParameterType(propSchema.Type),
|
||||
Location: pluginCommon.ParameterLocation_Body,
|
||||
}
|
||||
// Handle nested object type
|
||||
if propSchema.Type == domainDto.JsonSchemaType_OBJECT && len(propSchema.Properties) > 0 {
|
||||
param.SubParameters = convertFromJsonSchema(propSchema)
|
||||
}
|
||||
|
||||
// Handle array type
|
||||
if propSchema.Type == domainDto.JsonSchemaType_ARRAY && propSchema.Items != nil {
|
||||
param.Type = (mapJsonSchemaTypeToParameterType(propSchema.Items.Type))
|
||||
|
||||
if propSchema.Items.Type == domainDto.JsonSchemaType_OBJECT && len(propSchema.Items.Properties) > 0 {
|
||||
param.SubParameters = convertFromJsonSchema(propSchema.Items)
|
||||
}
|
||||
}
|
||||
parameters = append(parameters, param)
|
||||
}
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
|
||||
func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context, searchReq *domainDto.SearchSaasPluginRequest) ([]*entity.PluginInfo, bool, error) {
|
||||
|
||||
func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context) ([]*entity.PluginInfo, error) {
|
||||
searchReq := &domainDto.SearchSaasPluginRequest{}
|
||||
searchResp, err := p.searchSaasPlugin(ctx, searchReq)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to search SaaS plugins")
|
||||
return nil, false, errorx.Wrapf(err, "failed to search SaaS plugins")
|
||||
}
|
||||
|
||||
if searchResp == nil || searchResp.Data == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
plugins := make([]*entity.PluginInfo, 0, len(searchResp.Data.Items))
|
||||
@ -69,9 +230,7 @@ func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context) ([]*en
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
|
||||
logs.CtxInfof(ctx, "fetched %d SaaS plugins from coze.cn", len(plugins))
|
||||
|
||||
return plugins, nil
|
||||
return plugins, searchResp.Data.HasMore, nil
|
||||
}
|
||||
|
||||
func convertSaasPluginItemToEntity(item *domainDto.SaasPluginItem) *entity.PluginInfo {
|
||||
@ -81,11 +240,8 @@ func convertSaasPluginItemToEntity(item *domainDto.SaasPluginItem) *entity.Plugi
|
||||
|
||||
metaInfo := item.MetaInfo
|
||||
var pluginID int64
|
||||
if id, err := strconv.ParseInt(metaInfo.ProductID, 10, 64); err == nil {
|
||||
if id, err := strconv.ParseInt(metaInfo.EntityID, 10, 64); err == nil {
|
||||
pluginID = id
|
||||
} else {
|
||||
// 如果ID不是数字,使用简单的hash算法生成ID
|
||||
pluginID = int64(simpleHash(metaInfo.ProductID))
|
||||
}
|
||||
|
||||
// 创建插件清单
|
||||
@ -124,8 +280,6 @@ func convertCozePluginToEntity(cozePlugin CozePlugin) *entity.PluginInfo {
|
||||
var pluginID int64
|
||||
if id, err := strconv.ParseInt(cozePlugin.ID, 10, 64); err == nil {
|
||||
pluginID = id
|
||||
} else {
|
||||
pluginID = int64(simpleHash(cozePlugin.ID))
|
||||
}
|
||||
|
||||
manifest := &model.PluginManifest{
|
||||
@ -159,14 +313,6 @@ func convertCozePluginToEntity(cozePlugin CozePlugin) *entity.PluginInfo {
|
||||
return entity.NewPluginInfo(pluginInfo)
|
||||
}
|
||||
|
||||
func simpleHash(s string) uint32 {
|
||||
var hash uint32 = 5381
|
||||
for _, c := range s {
|
||||
hash = ((hash << 5) + hash) + uint32(c)
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
func (p *pluginServiceImpl) GetSaasPluginInfo(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error) {
|
||||
client := saasapi.NewCozeAPIClient()
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
pluginCommon "github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
||||
domainDto "github.com/coze-dev/coze-studio/backend/domain/plugin/dto"
|
||||
)
|
||||
|
||||
@ -167,4 +168,282 @@ func TestSearchSaasPluginResponse_WithAllNewFields(t *testing.T) {
|
||||
assert.NotNil(t, item.PluginInfo)
|
||||
assert.Equal(t, 5, item.PluginInfo.FavoriteCount)
|
||||
assert.Equal(t, int64(100), item.PluginInfo.CallCount)
|
||||
}
|
||||
|
||||
func TestJsonSchemaTypeUnmarshaling(t *testing.T) {
|
||||
// Test JSON data with inputSchema containing type field
|
||||
jsonData := `{
|
||||
"plugins": [{
|
||||
"tools": [{
|
||||
"tool_id": "7379227817307029513",
|
||||
"description": "当你需要获取网页、pdf、doc、docx、xlsx、csv、text 内容时,使用此工具",
|
||||
"name": "LinkReaderPlugin",
|
||||
"inputSchema": {
|
||||
"required": ["url"],
|
||||
"properties": {
|
||||
"need_image_url": {
|
||||
"description": "是否需要返回图片url",
|
||||
"type": "boolean"
|
||||
},
|
||||
"url": {
|
||||
"description": "网页url、pdf url、docx url、csv url、 xlsx url。",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"outputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
|
||||
var apiResp struct {
|
||||
Plugins []struct {
|
||||
Tools []struct {
|
||||
ToolID string `json:"tool_id"`
|
||||
Description string `json:"description"`
|
||||
InputSchema *domainDto.JsonSchema `json:"inputSchema"`
|
||||
Name string `json:"name"`
|
||||
OutputSchema *domainDto.JsonSchema `json:"outputSchema"`
|
||||
} `json:"tools"`
|
||||
} `json:"plugins"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(jsonData), &apiResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify that we have the expected structure
|
||||
assert.Len(t, apiResp.Plugins, 1)
|
||||
assert.Len(t, apiResp.Plugins[0].Tools, 1)
|
||||
|
||||
tool := apiResp.Plugins[0].Tools[0]
|
||||
assert.Equal(t, "7379227817307029513", tool.ToolID)
|
||||
assert.Equal(t, "LinkReaderPlugin", tool.Name)
|
||||
|
||||
// Verify InputSchema type field is correctly parsed
|
||||
assert.NotNil(t, tool.InputSchema)
|
||||
assert.Equal(t, domainDto.JsonSchemaType_OBJECT, tool.InputSchema.Type)
|
||||
assert.Len(t, tool.InputSchema.Required, 1)
|
||||
assert.Equal(t, "url", tool.InputSchema.Required[0])
|
||||
|
||||
// Verify properties are correctly parsed with their types
|
||||
assert.NotNil(t, tool.InputSchema.Properties)
|
||||
assert.Len(t, tool.InputSchema.Properties, 2)
|
||||
|
||||
urlProp := tool.InputSchema.Properties["url"]
|
||||
assert.NotNil(t, urlProp)
|
||||
assert.Equal(t, domainDto.JsonSchemaType_STRING, urlProp.Type)
|
||||
assert.Equal(t, "网页url、pdf url、docx url、csv url、 xlsx url。", urlProp.Description)
|
||||
|
||||
needImageProp := tool.InputSchema.Properties["need_image_url"]
|
||||
assert.NotNil(t, needImageProp)
|
||||
assert.Equal(t, domainDto.JsonSchemaType_BOOLEAN, needImageProp.Type)
|
||||
assert.Equal(t, "是否需要返回图片url", needImageProp.Description)
|
||||
|
||||
// Verify OutputSchema type field is correctly parsed
|
||||
assert.NotNil(t, tool.OutputSchema)
|
||||
assert.Equal(t, domainDto.JsonSchemaType_OBJECT, tool.OutputSchema.Type)
|
||||
}
|
||||
|
||||
func TestConvertFromJsonSchemaWithFixedType(t *testing.T) {
|
||||
// Create a JsonSchema with the type field properly set
|
||||
schema := &domainDto.JsonSchema{
|
||||
Type: domainDto.JsonSchemaType_OBJECT,
|
||||
Properties: map[string]*domainDto.JsonSchema{
|
||||
"url": {
|
||||
Type: domainDto.JsonSchemaType_STRING,
|
||||
Description: "网页url",
|
||||
},
|
||||
"need_image_url": {
|
||||
Type: domainDto.JsonSchemaType_BOOLEAN,
|
||||
Description: "是否需要返回图片url",
|
||||
},
|
||||
},
|
||||
Required: []string{"url"},
|
||||
}
|
||||
|
||||
// Test the convertFromJsonSchema function
|
||||
parameters := convertFromJsonSchema(schema)
|
||||
|
||||
// Debug: print the actual values
|
||||
t.Logf("Number of parameters: %d", len(parameters))
|
||||
for i, param := range parameters {
|
||||
t.Logf("Parameter %d: Name=%s, Location=%d, Type=%d", i, param.Name, param.Location, param.Type)
|
||||
}
|
||||
|
||||
// Verify that parameters are correctly generated
|
||||
assert.Len(t, parameters, 2)
|
||||
|
||||
// Find the url parameter
|
||||
var urlParam, imageParam *pluginCommon.APIParameter
|
||||
for _, param := range parameters {
|
||||
if param.Name == "url" {
|
||||
urlParam = param
|
||||
} else if param.Name == "need_image_url" {
|
||||
imageParam = param
|
||||
}
|
||||
}
|
||||
|
||||
// Verify url parameter
|
||||
assert.NotNil(t, urlParam)
|
||||
assert.Equal(t, "url", urlParam.Name)
|
||||
assert.Equal(t, "网页url", urlParam.Desc)
|
||||
assert.True(t, urlParam.IsRequired)
|
||||
assert.Equal(t, pluginCommon.ParameterType_String, urlParam.Type)
|
||||
assert.Equal(t, pluginCommon.ParameterLocation_Body, urlParam.Location)
|
||||
|
||||
// Verify need_image_url parameter
|
||||
assert.NotNil(t, imageParam)
|
||||
assert.Equal(t, "need_image_url", imageParam.Name)
|
||||
assert.Equal(t, "是否需要返回图片url", imageParam.Desc)
|
||||
assert.False(t, imageParam.IsRequired) // not in required array
|
||||
assert.Equal(t, pluginCommon.ParameterType_Bool, imageParam.Type)
|
||||
assert.Equal(t, pluginCommon.ParameterLocation_Body, imageParam.Location)
|
||||
}
|
||||
|
||||
func TestBatchGetSaasPluginToolsInfoIntegration(t *testing.T) {
|
||||
// This test simulates the original issue scenario
|
||||
// Create mock response data similar to what was provided in the issue
|
||||
respData := `{
|
||||
"plugins": [{
|
||||
"tools": [{
|
||||
"tool_id": "7379227817307029513",
|
||||
"description": "当你需要获取网页、pdf、doc、docx、xlsx、csv、text 内容时,使用此工具,可以获取url链接下的标题和内容。由于个别网站自身站点限制,无法获取网页内容。",
|
||||
"name": "LinkReaderPlugin",
|
||||
"inputSchema": {
|
||||
"required": ["url"],
|
||||
"properties": {
|
||||
"need_image_url": {
|
||||
"description": "是否需要返回图片url",
|
||||
"type": "boolean"
|
||||
},
|
||||
"url": {
|
||||
"description": "网页url、pdf url、docx url、csv url、 xlsx url。",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"outputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"properties": {
|
||||
"images": {
|
||||
"items": {
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"url": {"type": "string"},
|
||||
"width": {"type": "integer"},
|
||||
"alt": {"type": "string"},
|
||||
"height": {"type": "integer"}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"title": {"type": "string"},
|
||||
"content": {"type": "string"}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"err_msg": {
|
||||
"description": "错误信息",
|
||||
"type": "string"
|
||||
},
|
||||
"error_code": {
|
||||
"description": "错误码",
|
||||
"type": "string"
|
||||
},
|
||||
"error_msg": {
|
||||
"description": "错误信息",
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"description": "错误信息",
|
||||
"type": "string"
|
||||
},
|
||||
"pdf_content": {
|
||||
"description": "pdf的内容",
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"description": "错误码",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}]
|
||||
}]
|
||||
}`
|
||||
|
||||
// Simulate the unmarshaling process that happens in BatchGetSaasPluginToolsInfo
|
||||
var apiResp struct {
|
||||
Plugins []struct {
|
||||
Tools []struct {
|
||||
ToolID string `json:"tool_id"`
|
||||
Description string `json:"description"`
|
||||
InputSchema *domainDto.JsonSchema `json:"inputSchema"`
|
||||
Name string `json:"name"`
|
||||
OutputSchema *domainDto.JsonSchema `json:"outputSchema"`
|
||||
} `json:"tools"`
|
||||
McpJSON string `json:"mcp_json"`
|
||||
} `json:"plugins"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(respData), &apiResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify the structure is correctly parsed
|
||||
assert.Len(t, apiResp.Plugins, 1)
|
||||
assert.Len(t, apiResp.Plugins[0].Tools, 1)
|
||||
|
||||
tool := apiResp.Plugins[0].Tools[0]
|
||||
|
||||
// Verify InputSchema type is correctly parsed (this was the original issue)
|
||||
assert.NotNil(t, tool.InputSchema)
|
||||
assert.Equal(t, domainDto.JsonSchemaType_OBJECT, tool.InputSchema.Type)
|
||||
|
||||
// Now test the convertFromJsonSchema function with the parsed schema
|
||||
parameters := convertFromJsonSchema(tool.InputSchema)
|
||||
|
||||
// This should NOT be empty anymore (this was the original problem)
|
||||
assert.NotEmpty(t, parameters)
|
||||
assert.Len(t, parameters, 2)
|
||||
|
||||
// Verify the parameters are correctly generated
|
||||
paramMap := make(map[string]*pluginCommon.APIParameter)
|
||||
for _, param := range parameters {
|
||||
paramMap[param.Name] = param
|
||||
}
|
||||
|
||||
// Check url parameter
|
||||
urlParam := paramMap["url"]
|
||||
assert.NotNil(t, urlParam)
|
||||
assert.Equal(t, "url", urlParam.Name)
|
||||
assert.True(t, urlParam.IsRequired)
|
||||
assert.Equal(t, pluginCommon.ParameterType_String, urlParam.Type)
|
||||
assert.Equal(t, pluginCommon.ParameterLocation_Body, urlParam.Location)
|
||||
assert.Equal(t, "网页url、pdf url、docx url、csv url、 xlsx url。", urlParam.Desc)
|
||||
|
||||
// Check need_image_url parameter
|
||||
imageParam := paramMap["need_image_url"]
|
||||
assert.NotNil(t, imageParam)
|
||||
assert.Equal(t, "need_image_url", imageParam.Name)
|
||||
assert.False(t, imageParam.IsRequired)
|
||||
assert.Equal(t, pluginCommon.ParameterType_Bool, imageParam.Type)
|
||||
assert.Equal(t, pluginCommon.ParameterLocation_Body, imageParam.Location)
|
||||
assert.Equal(t, "是否需要返回图片url", imageParam.Desc)
|
||||
}
|
||||
@ -80,7 +80,8 @@ type PluginService interface {
|
||||
GetOAuthStatus(ctx context.Context, userID, pluginID int64) (resp *dto.GetOAuthStatusResponse, err error)
|
||||
GetAgentPluginsOAuthStatus(ctx context.Context, userID, agentID int64) (status []*dto.AgentPluginOAuthStatus, err error)
|
||||
//Saas Plugin Product
|
||||
ListSaasPluginProducts(ctx context.Context, req *dto.ListPluginProductsRequest) (resp *dto.ListPluginProductsResponse, err error)
|
||||
ListSaasPluginProducts(ctx context.Context, req *dto.ListSaasPluginProductsRequest) (resp *dto.ListPluginProductsResponse, err error)
|
||||
BatchGetSaasPluginToolsInfo(ctx context.Context, pluginIDs []int64) (tools map[int64][]*entity.ToolInfo, err error)
|
||||
GetSaasPluginInfo(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error)
|
||||
ListSaasPluginCategories(ctx context.Context, req *dto.ListPluginCategoriesRequest) (resp *dto.ListPluginCategoriesResponse, err error)
|
||||
|
||||
|
||||
@ -31,12 +31,14 @@ import (
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_common"
|
||||
pluginConsts "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/consts"
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin/model"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/plugin/internal/encoder"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/i18n"
|
||||
"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/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
|
||||
@ -156,6 +158,10 @@ func (h *httpCallImpl) buildHTTPRequest(ctx context.Context, args *InvocationArg
|
||||
|
||||
func (h *httpCallImpl) injectAuthInfo(ctx context.Context, httpReq *http.Request, args *InvocationArgs) (errMsg string, err error) {
|
||||
|
||||
if ptr.From(args.Tool.Source) == bot_common.PluginSource_FromSaas {
|
||||
return h.injectCozeSaasAPIToken(ctx, httpReq)
|
||||
}
|
||||
|
||||
if args.AuthInfo.MetaInfo.Type == pluginConsts.AuthzTypeOfNone {
|
||||
return "", nil
|
||||
}
|
||||
@ -279,6 +285,17 @@ func (h *httpCallImpl) buildRequestBody(ctx context.Context, op *model.Openapi3O
|
||||
return body, contentType, nil
|
||||
}
|
||||
|
||||
func(h *httpCallImpl) injectCozeSaasAPIToken (ctx context.Context, httpReq *http.Request) (errMsg string, err error){
|
||||
|
||||
// apiToken := os.Getenv(consts.CozeSaasAPIKey)
|
||||
apiToken := "pat_OjlRXGYdXDLHDv10dZuav02A7SomHZkXTjx0fbZ9xUDIrssE7tZ07gI2TzABBQ7M"
|
||||
if apiToken == "" {
|
||||
return "", fmt.Errorf("coze saas api token is empty")
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+apiToken)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (h *httpCallImpl) injectServiceAPIToken(ctx context.Context, httpReq *http.Request, authInfo *model.AuthV2) (errMsg string, err error) {
|
||||
if authInfo.SubType == pluginConsts.AuthzSubTypeOfServiceAPIToken {
|
||||
authOfAPIToken := authInfo.AuthOfAPIToken
|
||||
|
||||
@ -216,7 +216,7 @@ func (c *CozeAPIClient) requestWithQuery(ctx context.Context, method, path strin
|
||||
// Add API key if available
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
|
||||
req.Header.Set("X-TT-ENV", "ppe_cozemcpserver")
|
||||
req.Header.Set("X-TT-ENV", "ppe_cozeopenmcp")
|
||||
req.Header.Set("X-USE-PPE", "1")
|
||||
}
|
||||
|
||||
|
||||
@ -82,6 +82,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{
|
||||
"plugin": {
|
||||
"manifest": &plugin.PluginManifest{},
|
||||
"openapi_doc": &plugin.Openapi3T{},
|
||||
"ext": map[string]any{},
|
||||
},
|
||||
"plugin_draft": {
|
||||
"manifest": &plugin.PluginManifest{},
|
||||
@ -90,6 +91,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{
|
||||
"plugin_version": {
|
||||
"manifest": &plugin.PluginManifest{},
|
||||
"openapi_doc": &plugin.Openapi3T{},
|
||||
"ext": map[string]any{},
|
||||
},
|
||||
"agent_tool_draft": {
|
||||
"operation": &plugin.Openapi3Operation{},
|
||||
@ -99,12 +101,14 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{
|
||||
},
|
||||
"tool": {
|
||||
"operation": &plugin.Openapi3Operation{},
|
||||
"ext": map[string]any{},
|
||||
},
|
||||
"tool_draft": {
|
||||
"operation": &plugin.Openapi3Operation{},
|
||||
},
|
||||
"tool_version": {
|
||||
"operation": &plugin.Openapi3Operation{},
|
||||
"ext": map[string]any{},
|
||||
},
|
||||
"plugin_oauth_auth": {
|
||||
"oauth_config": &plugin.OAuthAuthorizationCodeConfig{},
|
||||
|
||||
8
docker/atlas/migrations/20250930034835_update.sql
Normal file
8
docker/atlas/migrations/20250930034835_update.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- Modify "plugin" table
|
||||
ALTER TABLE `opencoze`.`plugin` ADD COLUMN `source` tinyint NOT NULL DEFAULT 0 COMMENT "plugin source 1 from saas 0 default" AFTER `openapi_doc`, ADD COLUMN `ext` json NULL COMMENT "extra ";
|
||||
-- Modify "plugin_version" table
|
||||
ALTER TABLE `opencoze`.`plugin_version` ADD COLUMN `source` tinyint NOT NULL DEFAULT 0 COMMENT "plugin source 1 from saas 0 default" AFTER `openapi_doc`, ADD COLUMN `ext` json NULL COMMENT "extra ";
|
||||
-- Modify "tool" table
|
||||
ALTER TABLE `opencoze`.`tool` ADD COLUMN `source` tinyint NOT NULL DEFAULT 0 COMMENT "tool source 1 coze saas 0 default" AFTER `activated_status`, ADD COLUMN `ext` json NULL COMMENT "extra";
|
||||
-- Modify "tool_version" table
|
||||
ALTER TABLE `opencoze`.`tool_version` ADD COLUMN `source` tinyint NOT NULL DEFAULT 0 COMMENT "tool source 1 coze saas 0 default" AFTER `deleted_at`, ADD COLUMN `ext` json NULL COMMENT "extra";
|
||||
@ -1,4 +1,4 @@
|
||||
h1:pZ9P9pFFqnfVc+o3V1CJLD8Rv3WyFM+mSwqKk1GTRRs=
|
||||
h1:lmHhy0psjXX5nxglqxAu8GAdbWV+CFENuOc1AyPR0B8=
|
||||
20250703095335_initial.sql h1:/joaeUTMhXqAEc0KwsSve5+bYM0qPOp+9OizJtsRc+U=
|
||||
20250703115304_update.sql h1:cbYo6Q6Lh96hB4hu5KW2Nn/Mr0VDpg7a1WPgpIb1SOc=
|
||||
20250704040445_update.sql h1:QWmoPY//oQ+GFZwET9w/oAWa8mM0KVaD5G8Yiu9bMqY=
|
||||
@ -12,3 +12,4 @@ h1:pZ9P9pFFqnfVc+o3V1CJLD8Rv3WyFM+mSwqKk1GTRRs=
|
||||
20250812093734_update.sql h1:27fQaPt0LYi1dA7MABvERthVR4pj4MRWFgdRVR3cd6w=
|
||||
20250813081543_update.sql h1:HyBPu1LVs8oiyABbZDU3fFW0n6MeC7qOpzcHWVkwNVc=
|
||||
20250822060516_update.sql h1:KoL8FPXw5/JMsJMtJsoGFIc4wYHlngBudeYSz5o4iKU=
|
||||
20250930034835_update.sql h1:VgI4ss8IylY7Y4LrOj9s9PFvC37YB0z4SlOlB+n++d0=
|
||||
|
||||
Reference in New Issue
Block a user