feat(plugin): support saas plugin (#2349)

Co-authored-by: yuwenbinjie <yuwenbinjie@bytedance.com>
Co-authored-by: ski <csu.zengxiaohui@gmail.com>
Co-authored-by: Ryo <fanlv@bytedance.com>
This commit is contained in:
junwen-lee
2025-10-21 10:43:48 +08:00
committed by GitHub
parent d5e913d76d
commit 6e02c861c7
254 changed files with 48874 additions and 2399 deletions

View File

@ -1,72 +0,0 @@
/*
* 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 coze
import (
"bytes"
"context"
"net/http"
"testing"
"github.com/bytedance/sonic"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/common/ut"
"github.com/stretchr/testify/assert"
"github.com/coze-dev/coze-studio/backend/api/model/conversation/common"
"github.com/coze-dev/coze-studio/backend/api/model/conversation/conversation"
"github.com/coze-dev/coze-studio/backend/application"
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
)
func TestClearConversationCtx(t *testing.T) {
h := server.Default()
err := application.Init(context.Background())
t.Logf("application init err: %v", err)
h.POST("/api/conversation/create_section", ClearConversationCtx)
req := &conversation.ClearConversationCtxRequest{
ConversationID: 7496795464885338112,
Scene: ptr.Of(common.Scene_Playground),
}
m, err := sonic.Marshal(req)
assert.Nil(t, err)
w := ut.PerformRequest(h.Engine, "POST", "/api/conversation/create_section", &ut.Body{Body: bytes.NewBuffer(m), Len: len(m)}, ut.Header{Key: "Content-Type", Value: "application/json"})
res := w.Result()
t.Logf("clear conversation ctx: %s", res.Body())
assert.Equal(t, http.StatusInternalServerError, res.StatusCode())
}
func TestClearConversationHistory(t *testing.T) {
h := server.Default()
err := application.Init(context.Background())
t.Logf("application init err: %v", err)
h.POST("/api/conversation/clear_message", ClearConversationHistory)
req := &conversation.ClearConversationHistoryRequest{
ConversationID: 7496795464885338113,
Scene: ptr.Of(common.Scene_Playground),
BotID: ptr.Of(int64(7366055842027922437)),
}
m, err := sonic.Marshal(req)
assert.Nil(t, err)
w := ut.PerformRequest(h.Engine, "POST", "/api/conversation/clear_message", &ut.Body{Body: bytes.NewBuffer(m), Len: len(m)}, ut.Header{Key: "Content-Type", Value: "application/json"})
res := w.Result()
t.Logf("clear conversation history: %s", res.Body())
assert.Equal(t, http.StatusInternalServerError, res.StatusCode())
}

View File

@ -20,7 +20,9 @@ package coze
import (
"context"
"fmt"
"strconv"
"strings"
product_public_api "github.com/coze-dev/coze-studio/backend/api/model/marketplace/product_public_api"
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
@ -40,6 +42,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/logs"
)
// PublicGetProductList .
@ -68,6 +71,12 @@ func PublicGetProductList(ctx context.Context, c *app.RequestContext) {
internalServerErrorResponse(ctx, c, err)
return
}
case product_common.ProductEntityType_SaasPlugin:
resp, err = plugin.PluginApplicationSVC.GetCozeSaasPluginList(ctx, &req)
if err != nil {
internalServerErrorResponse(ctx, c, err)
return
}
}
c.JSON(consts.StatusOK, resp)
@ -267,3 +276,145 @@ func PublicDuplicateProduct(ctx context.Context, c *app.RequestContext) {
c.JSON(consts.StatusOK, resp)
}
// PublicSearchProduct .
// @router /api/marketplace/product/search [GET]
func PublicSearchProduct(ctx context.Context, c *app.RequestContext) {
var err error
var req product_public_api.SearchProductRequest
var categoryIDs []int64
if categoryIDsStr := string(c.Query("category_ids")); categoryIDsStr != "" {
categoryIDs, err = handlerCategoryIDs(c, &req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
c.Request.URI().QueryArgs().Del("category_ids")
}
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
if len(categoryIDs) > 0 {
req.CategoryIDs = categoryIDs
}
// Call plugin application service
resp, err := plugin.PluginApplicationSVC.PublicSearchProduct(ctx, &req)
if err != nil {
logs.CtxErrorf(ctx, "PublicSearchProduct failed: %v", err)
internalServerErrorResponse(ctx, c, err)
return
}
c.JSON(consts.StatusOK, resp)
}
func handlerCategoryIDs(c *app.RequestContext, req *product_public_api.SearchProductRequest) ([]int64, error) {
var categoryIDs []int64
if categoryIDsStr := string(c.Query("category_ids")); categoryIDsStr != "" {
categoryIDStrs := strings.Split(categoryIDsStr, ",")
categoryIDs = make([]int64, 0, len(categoryIDStrs))
for _, idStr := range categoryIDStrs {
idStr = strings.TrimSpace(idStr)
if idStr != "" {
// Validate that it's a valid integer
if categoryID, parseErr := strconv.ParseInt(idStr, 10, 64); parseErr == nil {
categoryIDs = append(categoryIDs, categoryID)
} else {
return nil, fmt.Errorf("invalid category_id: %s", idStr)
}
}
}
}
return categoryIDs, nil
}
// PublicSearchSuggest .
// @router /api/marketplace/product/search/suggest [GET]
func PublicSearchSuggest(ctx context.Context, c *app.RequestContext) {
var err error
var req product_public_api.SearchSuggestRequest
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
// Call plugin application service
resp, err := plugin.PluginApplicationSVC.PublicSearchSuggest(ctx, &req)
if err != nil {
logs.CtxErrorf(ctx, "PublicSearchSuggest failed: %v", err)
internalServerErrorResponse(ctx, c, err)
return
}
c.JSON(consts.StatusOK, resp)
}
// PublicGetProductCategoryList .
// @router /api/marketplace/product/category/list [GET]
func PublicGetProductCategoryList(ctx context.Context, c *app.RequestContext) {
var err error
var req product_public_api.GetProductCategoryListRequest
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
var resp *product_public_api.GetProductCategoryListResponse
req.EntityType = product_common.ProductEntityType_SaasPlugin
switch req.GetEntityType() {
case product_common.ProductEntityType_SaasPlugin:
resp, err = plugin.PluginApplicationSVC.GetSaasProductCategoryList(ctx, &req)
if err != nil {
internalServerErrorResponse(ctx, c, err)
return
}
}
c.JSON(consts.StatusOK, resp)
}
// PublicGetProductCallInfo .
// @router /api/marketplace/product/call_info [GET]
func PublicGetProductCallInfo(ctx context.Context, c *app.RequestContext) {
var err error
var req product_public_api.GetProductCallInfoRequest
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
resp, err := plugin.PluginApplicationSVC.GetProductCallInfo(ctx, &req)
if err != nil {
internalServerErrorResponse(ctx, c, err)
return
}
c.JSON(consts.StatusOK, resp)
}
// PublicGetMarketPluginConfig .
// @router /api/marketplace/product/config [GET]
func PublicGetMarketPluginConfig(ctx context.Context, c *app.RequestContext) {
var err error
var req product_public_api.GetMarketPluginConfigRequest
err = c.BindAndValidate(&req)
if err != nil {
invalidParamRequestResponse(c, err.Error())
return
}
resp, err := plugin.PluginApplicationSVC.GetMarketPluginConfig(ctx, &req)
if err != nil {
internalServerErrorResponse(ctx, c, err)
return
}
c.JSON(consts.StatusOK, resp)
}