feat(plugin): exec coze saas tool
This commit is contained in:
@ -51,6 +51,8 @@ func newPluginTools(ctx context.Context, conf *toolConfig) ([]tool.InvokableTool
|
||||
return model.VersionAgentTool{
|
||||
ToolID: a.GetApiId(),
|
||||
AgentVersion: ptr.Of(conf.agentIdentity.Version),
|
||||
PluginSource: a.PluginSource,
|
||||
PluginID: a.GetPluginId(),
|
||||
}
|
||||
}),
|
||||
}
|
||||
@ -69,10 +71,11 @@ func newPluginTools(ctx context.Context, conf *toolConfig) ([]tool.InvokableTool
|
||||
tools := make([]tool.InvokableTool, 0, len(agentTools))
|
||||
for _, ti := range agentTools {
|
||||
tools = append(tools, &pluginInvokableTool{
|
||||
userID: conf.userID,
|
||||
isDraft: conf.agentIdentity.IsDraft,
|
||||
projectInfo: projectInfo,
|
||||
toolInfo: ti,
|
||||
userID: conf.userID,
|
||||
isDraft: conf.agentIdentity.IsDraft,
|
||||
projectInfo: projectInfo,
|
||||
toolInfo: ti,
|
||||
pluginSource: ti.Source,
|
||||
|
||||
conversationID: conf.conversationID,
|
||||
})
|
||||
@ -87,6 +90,8 @@ type pluginInvokableTool struct {
|
||||
toolInfo *pluginEntity.ToolInfo
|
||||
projectInfo *model.ProjectInfo
|
||||
|
||||
pluginSource *bot_common.PluginSource
|
||||
|
||||
conversationID int64
|
||||
}
|
||||
|
||||
@ -117,6 +122,7 @@ func (p *pluginInvokableTool) InvokableRun(ctx context.Context, argumentsInJSON
|
||||
PluginID: p.toolInfo.PluginID,
|
||||
ToolID: p.toolInfo.ID,
|
||||
ExecDraftTool: false,
|
||||
PluginSource: p.pluginSource,
|
||||
ArgumentsInJson: argumentsInJSON,
|
||||
ExecScene: func() consts.ExecuteScene {
|
||||
if p.isDraft {
|
||||
|
||||
@ -81,7 +81,7 @@ func (p *pluginServiceImpl) MGetAgentTools(ctx context.Context, req *model.MGetA
|
||||
localToolIDs = append(localToolIDs, v.ToolID)
|
||||
}
|
||||
}
|
||||
// todo :: saas plugin or local plugin
|
||||
|
||||
existTools, err := p.toolRepo.MGetOnlineTools(ctx, localToolIDs, repository.WithToolID())
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "MGetOnlineTools failed, toolIDs=%v", localToolIDs)
|
||||
|
||||
@ -189,8 +189,20 @@ func (p *pluginServiceImpl) getDraftAgentPluginInfo(ctx context.Context, req *mo
|
||||
exist bool
|
||||
)
|
||||
if req.PluginSource != nil && *req.PluginSource == bot_common.PluginSource_FromSaas {
|
||||
//TODO::get plugin info from saas
|
||||
|
||||
tools, err := p.toolRepo.BatchGetSaasPluginToolsInfo(ctx, []int64{req.PluginID})
|
||||
if err != nil {
|
||||
return nil, nil, errorx.Wrapf(err, "BatchGetSaasPluginToolsInfo failed, pluginID=%d", req.PluginID)
|
||||
}
|
||||
if len(tools) == 0 {
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
}
|
||||
for _, tool := range tools[req.PluginID] {
|
||||
if tool.ID == req.ToolID {
|
||||
onlineTool = tool
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onlineTool, exist, err = p.toolRepo.GetOnlineTool(ctx, req.ToolID)
|
||||
if err != nil {
|
||||
@ -209,24 +221,35 @@ func (p *pluginServiceImpl) getDraftAgentPluginInfo(ctx context.Context, req *mo
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
}
|
||||
|
||||
if execOpt.ToolVersion == "" {
|
||||
onlinePlugin, exist, err = p.pluginRepo.GetOnlinePlugin(ctx, req.PluginID)
|
||||
if req.PluginSource != nil && *req.PluginSource == bot_common.PluginSource_FromSaas {
|
||||
saasPlugins, err := p.GetSaasPluginInfo(ctx, []int64{req.PluginID})
|
||||
if err != nil {
|
||||
return nil, nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", req.PluginID)
|
||||
return nil, nil, errorx.Wrapf(err, "GetSaasPluginInfo failed, pluginID=%d", req.PluginID)
|
||||
}
|
||||
if !exist {
|
||||
if len(saasPlugins) == 0 {
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
}
|
||||
onlinePlugin = saasPlugins[0]
|
||||
} else {
|
||||
onlinePlugin, exist, err = p.pluginRepo.GetVersionPlugin(ctx, model.VersionPlugin{
|
||||
PluginID: req.PluginID,
|
||||
Version: execOpt.ToolVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, errorx.Wrapf(err, "GetVersionPlugin failed, pluginID=%d, version=%s", req.PluginID, execOpt.ToolVersion)
|
||||
}
|
||||
if !exist {
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
if execOpt.ToolVersion == "" {
|
||||
onlinePlugin, exist, err = p.pluginRepo.GetOnlinePlugin(ctx, req.PluginID)
|
||||
if err != nil {
|
||||
return nil, nil, errorx.Wrapf(err, "GetOnlinePlugin failed, pluginID=%d", req.PluginID)
|
||||
}
|
||||
if !exist {
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
}
|
||||
} else {
|
||||
onlinePlugin, exist, err = p.pluginRepo.GetVersionPlugin(ctx, model.VersionPlugin{
|
||||
PluginID: req.PluginID,
|
||||
Version: execOpt.ToolVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, errorx.Wrapf(err, "GetVersionPlugin failed, pluginID=%d, version=%s", req.PluginID, execOpt.ToolVersion)
|
||||
}
|
||||
if !exist {
|
||||
return nil, nil, errorx.New(errno.ErrPluginRecordNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,8 +589,13 @@ func (t *toolExecutor) execute(ctx context.Context, argumentsInJson, accessToken
|
||||
}
|
||||
}
|
||||
|
||||
toolInvocation := newToolInvocation(t)
|
||||
requestStr, rawResp, err := toolInvocation.Do(ctx, invocation)
|
||||
var requestStr, rawResp string
|
||||
if t.plugin.Source != nil && *t.plugin.Source == bot_common.PluginSource_FromSaas {
|
||||
requestStr, rawResp, err = tool.NewSaasCallImpl().Do(ctx, invocation)
|
||||
} else {
|
||||
requestStr, rawResp, err = newToolInvocation(t).Do(ctx, invocation)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ func convertCozePluginToEntity(cozePlugin *dto.SaasPluginToolsList) *entity.Plug
|
||||
DeveloperID: 0,
|
||||
APPID: nil,
|
||||
IconURL: &cozePlugin.IconURL,
|
||||
ServerURL: ptr.Of(""),
|
||||
ServerURL: ptr.Of("https://api.coze.cn"),
|
||||
CreatedAt: cozePlugin.CreatedAt,
|
||||
UpdatedAt: cozePlugin.UpdatedAt,
|
||||
Manifest: manifest,
|
||||
|
||||
@ -31,15 +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/pkg/saasapi"
|
||||
"github.com/coze-dev/coze-studio/backend/types/consts"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
@ -158,10 +157,6 @@ 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
|
||||
}
|
||||
@ -287,12 +282,11 @@ func (h *httpCallImpl) buildRequestBody(ctx context.Context, op *model.Openapi3O
|
||||
|
||||
func (h *httpCallImpl) injectCozeSaasAPIToken(ctx context.Context, httpReq *http.Request) (errMsg string, err error) {
|
||||
|
||||
// apiToken := os.Getenv(consts.CozeSaasAPIKey)
|
||||
apiToken := "pat_OjlRXGYdXDLHDv10dZuav02A7SomHZkXTjx0fbZ9xUDIrssE7tZ07gI2TzABBQ7M"
|
||||
if apiToken == "" {
|
||||
saasapiClient := saasapi.NewCozeAPIClient()
|
||||
if saasapiClient.APIKey == "" {
|
||||
return "", fmt.Errorf("coze saas api token is empty")
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+apiToken)
|
||||
httpReq.Header.Set("Authorization", "Bearer "+saasapiClient.APIKey)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
||||
179
backend/domain/plugin/service/tool/invocation_saas.go
Normal file
179
backend/domain/plugin/service/tool/invocation_saas.go
Normal file
@ -0,0 +1,179 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"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/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/saasapi"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type saasCallImpl struct {
|
||||
}
|
||||
|
||||
func NewSaasCallImpl() Invocation {
|
||||
return &saasCallImpl{}
|
||||
}
|
||||
|
||||
func (s *saasCallImpl) Do(ctx context.Context, args *InvocationArgs) (request string, resp string, err error) {
|
||||
httpReq, err := s.buildHTTPRequest(ctx, args)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
err = s.injectAuthInfo(ctx, httpReq, args)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var reqBodyBytes []byte
|
||||
if httpReq.GetBody != nil {
|
||||
reqBody, err := httpReq.GetBody()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer reqBody.Close()
|
||||
|
||||
reqBodyBytes, err = io.ReadAll(reqBody)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
requestStr, err := genRequestString(httpReq, reqBodyBytes)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
restyReq := defaultHttpCli.NewRequest()
|
||||
restyReq.Header = httpReq.Header
|
||||
restyReq.Method = httpReq.Method
|
||||
restyReq.URL = httpReq.URL.String()
|
||||
if reqBodyBytes != nil {
|
||||
restyReq.SetBody(reqBodyBytes)
|
||||
}
|
||||
restyReq.SetContext(ctx)
|
||||
|
||||
logs.CtxDebugf(ctx, "[execute] url=%s, header=%s, method=%s, body=%s",
|
||||
restyReq.URL, restyReq.Header, restyReq.Method, restyReq.Body)
|
||||
|
||||
httpResp, err := restyReq.Send()
|
||||
if err != nil {
|
||||
return "", "", errorx.New(errno.ErrPluginExecuteToolFailed, errorx.KVf(errno.PluginMsgKey, "http request failed, err=%s", err))
|
||||
}
|
||||
|
||||
logs.CtxDebugf(ctx, "[execute] status=%s, response=%s", httpResp.Status(), httpResp.String())
|
||||
|
||||
if httpResp.StatusCode() != http.StatusOK {
|
||||
return "", "", errorx.New(errno.ErrPluginExecuteToolFailed,
|
||||
errorx.KVf(errno.PluginMsgKey, "http request failed, status=%s\nresp=%s", httpResp.Status(), httpResp.String()))
|
||||
}
|
||||
httpRespBody := httpResp.String()
|
||||
|
||||
type CozeAPIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
var apiResp CozeAPIResponse
|
||||
if err := sonic.UnmarshalString(httpRespBody, &apiResp); err != nil {
|
||||
return "", "", fmt.Errorf("failed to parse API response: %w", err)
|
||||
}
|
||||
|
||||
rawResp := apiResp.Data["result"]
|
||||
return requestStr, encoder.MustString(rawResp), nil
|
||||
}
|
||||
|
||||
func (s *saasCallImpl) injectAuthInfo(ctx context.Context, httpReq *http.Request, args *InvocationArgs) (err error) {
|
||||
|
||||
saasapiClient := saasapi.NewCozeAPIClient()
|
||||
if saasapiClient.APIKey == "" {
|
||||
return fmt.Errorf("coze saas api token is empty")
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+saasapiClient.APIKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *saasCallImpl) buildHTTPRequest(ctx context.Context, args *InvocationArgs) (httpReq *http.Request, err error) {
|
||||
tool := args.Tool
|
||||
rawURL := args.ServerURL + tool.GetSubURL()
|
||||
|
||||
reqURL, err := s.buildHTTPRequestURL(ctx, rawURL, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type callSaasTool struct {
|
||||
Arguments map[string]any `json:"arguments"`
|
||||
ToolName string `json:"tool_name"`
|
||||
}
|
||||
|
||||
callSaasToolData := &callSaasTool{
|
||||
ToolName: tool.GetName(),
|
||||
Arguments: args.Body,
|
||||
}
|
||||
|
||||
bodyBytes, err := sonic.MarshalString(callSaasToolData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq, err = http.NewRequestWithContext(ctx, tool.GetMethod(), reqURL.String(), bytes.NewBufferString(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return httpReq, nil
|
||||
}
|
||||
|
||||
func (s *saasCallImpl) buildHTTPRequestURL(ctx context.Context, rawURL string, args *InvocationArgs) (reqURL *url.URL, err error) {
|
||||
if len(args.Path) > 0 {
|
||||
for k, v := range args.Path {
|
||||
p := args.groupedKeySchema.PathKeys[k]
|
||||
vStr, eErr := encoder.EncodeParameter(p, v)
|
||||
if eErr != nil {
|
||||
return nil, eErr
|
||||
}
|
||||
rawURL = strings.ReplaceAll(rawURL, "{"+k+"}", vStr)
|
||||
}
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
if len(args.Query) > 0 {
|
||||
for k, val := range args.Query {
|
||||
switch v := val.(type) {
|
||||
case []any:
|
||||
for _, _v := range v {
|
||||
query.Add(k, encoder.MustString(_v))
|
||||
}
|
||||
default:
|
||||
query.Add(k, encoder.MustString(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encodeQuery := query.Encode()
|
||||
|
||||
reqURL, err = url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(reqURL.RawQuery) > 0 && len(encodeQuery) > 0 {
|
||||
reqURL.RawQuery += "&" + encodeQuery
|
||||
} else if len(encodeQuery) > 0 {
|
||||
reqURL.RawQuery = encodeQuery
|
||||
}
|
||||
|
||||
return reqURL, nil
|
||||
}
|
||||
Reference in New Issue
Block a user