582 lines
15 KiB
Go
582 lines
15 KiB
Go
/*
|
|
* Copyright 2025 coze-dev Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
productAPI "github.com/coze-dev/coze-studio/backend/api/model/marketplace/product_public_api"
|
|
"github.com/coze-dev/coze-studio/backend/api/model/plugin_develop/common"
|
|
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
|
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
|
"github.com/getkin/kin-openapi/openapi3"
|
|
gonanoid "github.com/matoous/go-nanoid"
|
|
)
|
|
|
|
type ToolInfo struct {
|
|
ID int64
|
|
PluginID int64
|
|
CreatedAt int64
|
|
UpdatedAt int64
|
|
Version *string
|
|
|
|
ActivatedStatus *ActivatedStatus
|
|
DebugStatus *common.APIDebugStatus
|
|
|
|
Method *string
|
|
SubURL *string
|
|
Operation *Openapi3Operation
|
|
}
|
|
|
|
func (t ToolInfo) GetName() string {
|
|
if t.Operation == nil {
|
|
return ""
|
|
}
|
|
return t.Operation.OperationID
|
|
}
|
|
|
|
func (t ToolInfo) GetDesc() string {
|
|
if t.Operation == nil {
|
|
return ""
|
|
}
|
|
return t.Operation.Summary
|
|
}
|
|
|
|
func (t ToolInfo) GetVersion() string {
|
|
return ptr.FromOrDefault(t.Version, "")
|
|
}
|
|
|
|
func (t ToolInfo) GetActivatedStatus() ActivatedStatus {
|
|
return ptr.FromOrDefault(t.ActivatedStatus, ActivateTool)
|
|
}
|
|
|
|
func (t ToolInfo) GetSubURL() string {
|
|
return ptr.FromOrDefault(t.SubURL, "")
|
|
}
|
|
|
|
func (t ToolInfo) GetMethod() string {
|
|
return strings.ToUpper(ptr.FromOrDefault(t.Method, ""))
|
|
}
|
|
|
|
func (t ToolInfo) GetDebugStatus() common.APIDebugStatus {
|
|
return ptr.FromOrDefault(t.DebugStatus, common.APIDebugStatus_DebugWaiting)
|
|
}
|
|
|
|
func (t ToolInfo) GetResponseOpenapiSchema() (*openapi3.Schema, error) {
|
|
op := t.Operation
|
|
if op == nil {
|
|
return nil, fmt.Errorf("operation is required")
|
|
}
|
|
|
|
resp, ok := op.Responses[strconv.Itoa(http.StatusOK)]
|
|
if !ok || resp == nil || resp.Value == nil || len(resp.Value.Content) == 0 {
|
|
return nil, fmt.Errorf("response status '200' not found")
|
|
}
|
|
|
|
mType, ok := resp.Value.Content[MediaTypeJson] // only support application/json
|
|
if !ok || mType == nil || mType.Schema == nil || mType.Schema.Value == nil {
|
|
return nil, fmt.Errorf("media type '%s' not found in response", MediaTypeJson)
|
|
}
|
|
|
|
return mType.Schema.Value, nil
|
|
}
|
|
|
|
type paramMetaInfo struct {
|
|
name string
|
|
desc string
|
|
required bool
|
|
location string
|
|
}
|
|
|
|
func (t ToolInfo) ToRespAPIParameter() ([]*common.APIParameter, error) {
|
|
op := t.Operation
|
|
if op == nil {
|
|
return nil, fmt.Errorf("operation is required")
|
|
}
|
|
|
|
respSchema, err := t.GetResponseOpenapiSchema()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params := make([]*common.APIParameter, 0, len(op.Parameters))
|
|
if len(respSchema.Properties) == 0 {
|
|
return params, nil
|
|
}
|
|
|
|
required := slices.ToMap(respSchema.Required, func(e string) (string, bool) {
|
|
return e, true
|
|
})
|
|
|
|
for subParamName, prop := range respSchema.Properties {
|
|
if prop == nil || prop.Value == nil {
|
|
return nil, fmt.Errorf("the schema of property '%s' is required", subParamName)
|
|
}
|
|
|
|
paramMeta := paramMetaInfo{
|
|
name: subParamName,
|
|
desc: prop.Value.Description,
|
|
location: string(ParamInBody),
|
|
required: required[subParamName],
|
|
}
|
|
apiParam, err := toAPIParameter(paramMeta, prop.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params = append(params, apiParam)
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
func (t ToolInfo) ToReqAPIParameter() ([]*common.APIParameter, error) {
|
|
op := t.Operation
|
|
if op == nil {
|
|
return nil, fmt.Errorf("operation is required")
|
|
}
|
|
|
|
params := make([]*common.APIParameter, 0, len(op.Parameters))
|
|
for _, param := range op.Parameters {
|
|
if param == nil || param.Value == nil || param.Value.Schema == nil || param.Value.Schema.Value == nil {
|
|
return nil, fmt.Errorf("parameter schema is required")
|
|
}
|
|
|
|
paramVal := param.Value
|
|
schemaVal := paramVal.Schema.Value
|
|
|
|
if schemaVal.Type == openapi3.TypeObject {
|
|
return nil, fmt.Errorf("the type of parameter '%s' cannot be 'object'", paramVal.Name)
|
|
}
|
|
|
|
if schemaVal.Type == openapi3.TypeArray {
|
|
if paramVal.In == openapi3.ParameterInPath {
|
|
return nil, fmt.Errorf("the type of field '%s' cannot be 'array'", paramVal.Name)
|
|
}
|
|
if schemaVal.Items == nil || schemaVal.Items.Value == nil {
|
|
return nil, fmt.Errorf("the item schema of field '%s' is required", paramVal.Name)
|
|
}
|
|
item := schemaVal.Items.Value
|
|
if item.Type == openapi3.TypeObject || item.Type == openapi3.TypeArray {
|
|
return nil, fmt.Errorf("the item type of parameter '%s' cannot be 'object' or 'array'", paramVal.Name)
|
|
}
|
|
}
|
|
|
|
paramMeta := paramMetaInfo{
|
|
name: paramVal.Name,
|
|
desc: paramVal.Description,
|
|
location: paramVal.In,
|
|
required: paramVal.Required,
|
|
}
|
|
apiParam, err := toAPIParameter(paramMeta, schemaVal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params = append(params, apiParam)
|
|
}
|
|
|
|
if op.RequestBody == nil || op.RequestBody.Value == nil || len(op.RequestBody.Value.Content) == 0 {
|
|
return params, nil
|
|
}
|
|
|
|
for _, mType := range op.RequestBody.Value.Content {
|
|
if mType == nil || mType.Schema == nil || mType.Schema.Value == nil {
|
|
return nil, fmt.Errorf("request body schema is required")
|
|
}
|
|
|
|
schemaVal := mType.Schema.Value
|
|
if len(schemaVal.Properties) == 0 {
|
|
continue
|
|
}
|
|
|
|
required := slices.ToMap(schemaVal.Required, func(e string) (string, bool) {
|
|
return e, true
|
|
})
|
|
|
|
for subParamName, prop := range schemaVal.Properties {
|
|
if prop == nil || prop.Value == nil {
|
|
return nil, fmt.Errorf("the schema of property '%s' is required", subParamName)
|
|
}
|
|
|
|
paramMeta := paramMetaInfo{
|
|
name: subParamName,
|
|
desc: prop.Value.Description,
|
|
location: string(ParamInBody),
|
|
required: required[subParamName],
|
|
}
|
|
apiParam, err := toAPIParameter(paramMeta, prop.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
params = append(params, apiParam)
|
|
}
|
|
|
|
break // Take only one MIME.
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
func toAPIParameter(paramMeta paramMetaInfo, sc *openapi3.Schema) (*common.APIParameter, error) {
|
|
if sc == nil {
|
|
return nil, fmt.Errorf("schema is requred")
|
|
}
|
|
|
|
apiType, ok := ToThriftParamType(strings.ToLower(sc.Type))
|
|
if !ok {
|
|
return nil, fmt.Errorf("the type '%s' of filed '%s' is invalid", sc.Type, paramMeta.name)
|
|
}
|
|
location, ok := ToThriftHTTPParamLocation(HTTPParamLocation(paramMeta.location))
|
|
if !ok {
|
|
return nil, fmt.Errorf("the location '%s' of field '%s' is invalid", paramMeta.location, paramMeta.name)
|
|
}
|
|
|
|
apiParam := &common.APIParameter{
|
|
ID: gonanoid.MustID(10),
|
|
Name: paramMeta.name,
|
|
Desc: paramMeta.desc,
|
|
Type: apiType,
|
|
Location: location, // Using the value of the parent node
|
|
IsRequired: paramMeta.required,
|
|
SubParameters: []*common.APIParameter{},
|
|
}
|
|
|
|
if sc.Default != nil {
|
|
apiParam.GlobalDefault = ptr.Of(fmt.Sprintf("%v", sc.Default))
|
|
apiParam.LocalDefault = ptr.Of(fmt.Sprintf("%v", sc.Default))
|
|
}
|
|
|
|
if sc.Format != "" {
|
|
aType, ok := FormatToAssistType(sc.Format)
|
|
if !ok {
|
|
return nil, fmt.Errorf("the format '%s' of field '%s' is invalid", sc.Format, paramMeta.name)
|
|
}
|
|
_aType, ok := ToThriftAPIAssistType(aType)
|
|
if !ok {
|
|
return nil, fmt.Errorf("assist type '%s' of field '%s' is invalid", aType, paramMeta.name)
|
|
}
|
|
apiParam.AssistType = ptr.Of(_aType)
|
|
}
|
|
|
|
if v, ok := sc.Extensions[APISchemaExtendGlobalDisable]; ok {
|
|
if disable, ok := v.(bool); ok {
|
|
apiParam.GlobalDisable = disable
|
|
}
|
|
}
|
|
if v, ok := sc.Extensions[APISchemaExtendLocalDisable]; ok {
|
|
if disable, ok := v.(bool); ok {
|
|
apiParam.LocalDisable = disable
|
|
}
|
|
}
|
|
if v, ok := sc.Extensions[APISchemaExtendVariableRef]; ok {
|
|
if ref, ok := v.(string); ok {
|
|
apiParam.VariableRef = ptr.Of(ref)
|
|
apiParam.DefaultParamSource = ptr.Of(common.DefaultParamSource_Variable)
|
|
}
|
|
}
|
|
|
|
switch sc.Type {
|
|
case openapi3.TypeObject:
|
|
if len(sc.Properties) == 0 {
|
|
return apiParam, nil
|
|
}
|
|
|
|
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
|
|
return e, true
|
|
})
|
|
for subParamName, prop := range sc.Properties {
|
|
if prop == nil || prop.Value == nil {
|
|
return nil, fmt.Errorf("the schema of property '%s' is required", subParamName)
|
|
}
|
|
|
|
subMeta := paramMetaInfo{
|
|
name: subParamName,
|
|
desc: prop.Value.Description,
|
|
required: required[subParamName],
|
|
location: paramMeta.location,
|
|
}
|
|
subParam, err := toAPIParameter(subMeta, prop.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apiParam.SubParameters = append(apiParam.SubParameters, subParam)
|
|
}
|
|
|
|
return apiParam, nil
|
|
|
|
case openapi3.TypeArray:
|
|
if sc.Items == nil || sc.Items.Value == nil {
|
|
return nil, fmt.Errorf("the item schema of field '%s' is required", paramMeta.name)
|
|
}
|
|
|
|
item := sc.Items.Value
|
|
|
|
subMeta := paramMetaInfo{
|
|
name: "[Array Item]",
|
|
desc: item.Description,
|
|
location: paramMeta.location,
|
|
required: paramMeta.required,
|
|
}
|
|
subParam, err := toAPIParameter(subMeta, item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apiParam.SubParameters = append(apiParam.SubParameters, subParam)
|
|
|
|
return apiParam, nil
|
|
}
|
|
|
|
return apiParam, nil
|
|
}
|
|
|
|
func (t ToolInfo) ToPluginParameters() ([]*common.PluginParameter, error) {
|
|
op := t.Operation
|
|
if op == nil {
|
|
return nil, fmt.Errorf("operation is required")
|
|
}
|
|
|
|
var params []*common.PluginParameter
|
|
|
|
for _, prop := range op.Parameters {
|
|
if prop == nil || prop.Value == nil || prop.Value.Schema == nil || prop.Value.Schema.Value == nil {
|
|
return nil, fmt.Errorf("parameter schema is required")
|
|
}
|
|
|
|
paramVal := prop.Value
|
|
schemaVal := paramVal.Schema.Value
|
|
|
|
if schemaVal.Type == openapi3.TypeObject {
|
|
return nil, fmt.Errorf("the type of parameter '%s' cannot be 'object'", paramVal.Name)
|
|
}
|
|
|
|
var arrayItemType string
|
|
if schemaVal.Type == openapi3.TypeArray {
|
|
if paramVal.In == openapi3.ParameterInPath {
|
|
return nil, fmt.Errorf("the type of field '%s' cannot be 'array'", paramVal.Name)
|
|
}
|
|
if schemaVal.Items == nil || schemaVal.Items.Value == nil {
|
|
return nil, fmt.Errorf("the item schema of field '%s' is required", paramVal.Name)
|
|
}
|
|
item := schemaVal.Items.Value
|
|
if item.Type == openapi3.TypeObject || item.Type == openapi3.TypeArray {
|
|
return nil, fmt.Errorf("the item type of parameter '%s' cannot be 'object' or 'array'", paramVal.Name)
|
|
}
|
|
|
|
arrayItemType = item.Type
|
|
}
|
|
|
|
if disabledParam(schemaVal) {
|
|
continue
|
|
}
|
|
|
|
var assistType *common.PluginParamTypeFormat
|
|
if v, ok := schemaVal.Extensions[APISchemaExtendAssistType]; ok {
|
|
_v, ok := v.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
f, ok := AssistTypeToThriftFormat(APIFileAssistType(_v))
|
|
if ok {
|
|
return nil, fmt.Errorf("the assist type '%s' of field '%s' is invalid", _v, paramVal.Name)
|
|
}
|
|
assistType = ptr.Of(f)
|
|
}
|
|
|
|
params = append(params, &common.PluginParameter{
|
|
Name: paramVal.Name,
|
|
Desc: paramVal.Description,
|
|
Required: paramVal.Required,
|
|
Type: schemaVal.Type,
|
|
SubType: arrayItemType,
|
|
Format: assistType,
|
|
SubParameters: []*common.PluginParameter{},
|
|
})
|
|
}
|
|
|
|
if op.RequestBody == nil || op.RequestBody.Value == nil || len(op.RequestBody.Value.Content) == 0 {
|
|
return params, nil
|
|
}
|
|
|
|
for _, mType := range op.RequestBody.Value.Content {
|
|
if mType == nil || mType.Schema == nil || mType.Schema.Value == nil {
|
|
return nil, fmt.Errorf("request body schema is required")
|
|
}
|
|
|
|
schemaVal := mType.Schema.Value
|
|
if len(schemaVal.Properties) == 0 {
|
|
continue
|
|
}
|
|
|
|
required := slices.ToMap(schemaVal.Required, func(e string) (string, bool) {
|
|
return e, true
|
|
})
|
|
|
|
for subParamName, prop := range schemaVal.Properties {
|
|
if prop == nil || prop.Value == nil {
|
|
return nil, fmt.Errorf("the schema of property '%s' is required", subParamName)
|
|
}
|
|
|
|
paramMeta := paramMetaInfo{
|
|
name: subParamName,
|
|
desc: prop.Value.Description,
|
|
required: required[subParamName],
|
|
}
|
|
paramInfo, err := toPluginParameter(paramMeta, prop.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if paramInfo != nil {
|
|
params = append(params, paramInfo)
|
|
}
|
|
}
|
|
|
|
break // Take only one MIME.
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
func toPluginParameter(paramMeta paramMetaInfo, sc *openapi3.Schema) (*common.PluginParameter, error) {
|
|
if sc == nil {
|
|
return nil, fmt.Errorf("schema is required")
|
|
}
|
|
|
|
if disabledParam(sc) {
|
|
return nil, nil
|
|
}
|
|
|
|
var assistType *common.PluginParamTypeFormat
|
|
if v, ok := sc.Extensions[APISchemaExtendAssistType]; ok {
|
|
if _v, ok := v.(string); ok {
|
|
f, ok := AssistTypeToThriftFormat(APIFileAssistType(_v))
|
|
if !ok {
|
|
return nil, fmt.Errorf("the assist type '%s' of field '%s' is invalid", _v, paramMeta.name)
|
|
}
|
|
assistType = ptr.Of(f)
|
|
}
|
|
}
|
|
|
|
pluginParam := &common.PluginParameter{
|
|
Name: paramMeta.name,
|
|
Type: sc.Type,
|
|
Desc: paramMeta.desc,
|
|
Required: paramMeta.required,
|
|
Format: assistType,
|
|
SubParameters: []*common.PluginParameter{},
|
|
}
|
|
|
|
switch sc.Type {
|
|
case openapi3.TypeObject:
|
|
if len(sc.Properties) == 0 {
|
|
return pluginParam, nil
|
|
}
|
|
|
|
required := slices.ToMap(sc.Required, func(e string) (string, bool) {
|
|
return e, true
|
|
})
|
|
for subParamName, prop := range sc.Properties {
|
|
if prop == nil || prop.Value == nil {
|
|
return nil, fmt.Errorf("the schema of property '%s' is required", subParamName)
|
|
}
|
|
|
|
subMeta := paramMetaInfo{
|
|
name: subParamName,
|
|
desc: prop.Value.Description,
|
|
required: required[subParamName],
|
|
}
|
|
subParam, err := toPluginParameter(subMeta, prop.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pluginParam.SubParameters = append(pluginParam.SubParameters, subParam)
|
|
}
|
|
|
|
return pluginParam, nil
|
|
|
|
case openapi3.TypeArray:
|
|
if sc.Items == nil || sc.Items.Value == nil {
|
|
return nil, fmt.Errorf("the item schema of field '%s' is required", paramMeta.name)
|
|
}
|
|
|
|
item := sc.Items.Value
|
|
pluginParam.SubType = item.Type
|
|
|
|
if item.Type != openapi3.TypeObject {
|
|
return pluginParam, nil
|
|
}
|
|
|
|
subMeta := paramMetaInfo{
|
|
desc: item.Description,
|
|
required: paramMeta.required,
|
|
}
|
|
subParam, err := toPluginParameter(subMeta, item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pluginParam.SubParameters = append(pluginParam.SubParameters, subParam.SubParameters...)
|
|
|
|
return pluginParam, nil
|
|
}
|
|
|
|
return pluginParam, nil
|
|
}
|
|
|
|
func (t ToolInfo) ToToolParameters() ([]*productAPI.ToolParameter, error) {
|
|
apiParams, err := t.ToReqAPIParameter()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var toToolParams func(apiParams []*common.APIParameter) ([]*productAPI.ToolParameter, error)
|
|
toToolParams = func(apiParams []*common.APIParameter) ([]*productAPI.ToolParameter, error) {
|
|
params := make([]*productAPI.ToolParameter, 0, len(apiParams))
|
|
for _, apiParam := range apiParams {
|
|
typ, _ := ToOpenapiParamType(apiParam.Type)
|
|
toolParam := &productAPI.ToolParameter{
|
|
Name: apiParam.Name,
|
|
Description: apiParam.Desc,
|
|
Type: typ,
|
|
IsRequired: apiParam.IsRequired,
|
|
SubParameter: []*productAPI.ToolParameter{},
|
|
}
|
|
|
|
if len(apiParam.SubParameters) > 0 {
|
|
subParams, err := toToolParams(apiParam.SubParameters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
toolParam.SubParameter = append(toolParam.SubParameter, subParams...)
|
|
}
|
|
|
|
params = append(params, toolParam)
|
|
}
|
|
|
|
return params, nil
|
|
}
|
|
|
|
return toToolParams(apiParams)
|
|
}
|