feat: sync remaining channel and relay updates

This commit is contained in:
OpenClaw Task Bot
2026-04-27 08:56:38 +08:00
parent 0321101ff7
commit 58c0258362
18 changed files with 465 additions and 271 deletions

View File

@@ -2,13 +2,16 @@ package controller
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/textproto"
"net/url"
"strconv"
"strings"
@@ -42,6 +45,56 @@ type testResult struct {
newAPIError *types.NewAPIError
}
const imageEditTestPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wn7Zc8AAAAASUVORK5CYII="
func buildImageEditTestMultipartRequest(requestPath, model string) (*http.Request, error) {
var requestBody bytes.Buffer
writer := multipart.NewWriter(&requestBody)
if err := writer.WriteField("model", model); err != nil {
return nil, err
}
if err := writer.WriteField("prompt", "edit this image"); err != nil {
return nil, err
}
if err := writer.WriteField("size", "1024x1024"); err != nil {
return nil, err
}
if err := writer.WriteField("n", "1"); err != nil {
return nil, err
}
imageBytes, err := base64.StdEncoding.DecodeString(imageEditTestPngBase64)
if err != nil {
return nil, err
}
header := make(textproto.MIMEHeader)
header.Set("Content-Disposition", `form-data; name="image"; filename="channel-test.png"`)
header.Set("Content-Type", "image/png")
part, err := writer.CreatePart(header)
if err != nil {
return nil, err
}
if _, err = part.Write(imageBytes); err != nil {
return nil, err
}
if err = writer.Close(); err != nil {
return nil, err
}
req := &http.Request{
Method: "POST",
URL: &url.URL{Path: requestPath},
Body: io.NopCloser(bytes.NewReader(requestBody.Bytes())),
Header: make(http.Header),
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.ContentLength = int64(requestBody.Len())
return req, nil
}
func normalizeChannelTestEndpoint(channel *model.Channel, modelName, endpointType string) string {
normalized := strings.TrimSpace(endpointType)
if normalized != "" {
@@ -153,6 +206,16 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
//c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
c.Request.Header.Set("Content-Type", "application/json")
if requestPath == "/v1/images/edits" {
c.Request, err = buildImageEditTestMultipartRequest(requestPath, testModel)
if err != nil {
return testResult{
context: c,
localErr: err,
newAPIError: types.NewError(err, types.ErrorCodeReadRequestBodyFailed),
}
}
}
c.Set("channel", channel.Type)
c.Set("base_url", channel.GetBaseURL())
group, _ := model.GetUserGroup(1, false)
@@ -184,7 +247,7 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
relayFormat = types.RelayFormatGemini
case constant.EndpointTypeJinaRerank:
relayFormat = types.RelayFormatRerank
case constant.EndpointTypeImageGeneration:
case constant.EndpointTypeImageGeneration, constant.EndpointTypeImageEdit:
relayFormat = types.RelayFormatOpenAIImage
case constant.EndpointTypeEmbeddings:
relayFormat = types.RelayFormatEmbedding
@@ -200,6 +263,9 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
if c.Request.URL.Path == "/v1/images/generations" {
relayFormat = types.RelayFormatOpenAIImage
}
if c.Request.URL.Path == "/v1/images/edits" {
relayFormat = types.RelayFormatOpenAIImage
}
if c.Request.URL.Path == "/v1/messages" {
relayFormat = types.RelayFormatClaude
}
@@ -294,7 +360,7 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
newAPIError: types.NewError(errors.New("invalid embedding request type"), types.ErrorCodeConvertRequestFailed),
}
}
case relayconstant.RelayModeImagesGenerations:
case relayconstant.RelayModeImagesGenerations, relayconstant.RelayModeImagesEdits:
// 图像生成请求 - request 已经是正确的类型
if imageReq, ok := request.(*dto.ImageRequest); ok {
convertedRequest, err = adaptor.ConvertImageRequest(c, info, *imageReq)
@@ -366,44 +432,53 @@ func testChannel(channel *model.Channel, testModel string, endpointType string,
newAPIError: types.NewError(err, types.ErrorCodeConvertRequestFailed),
}
}
jsonData, err := common.Marshal(convertedRequest)
if err != nil {
return testResult{
context: c,
localErr: err,
newAPIError: types.NewError(err, types.ErrorCodeJsonMarshalFailed),
}
}
//jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings)
//if err != nil {
// return testResult{
// context: c,
// localErr: err,
// newAPIError: types.NewError(err, types.ErrorCodeConvertRequestFailed),
// }
//}
if len(info.ParamOverride) > 0 {
jsonData, err = relaycommon.ApplyParamOverrideWithRelayInfo(jsonData, info)
if err != nil {
if fixedErr, ok := relaycommon.AsParamOverrideReturnError(err); ok {
return testResult{
context: c,
localErr: fixedErr,
newAPIError: relaycommon.NewAPIErrorFromParamOverride(fixedErr),
}
}
var requestBody *bytes.Buffer
switch req := convertedRequest.(type) {
case *bytes.Buffer:
requestBody = bytes.NewBuffer(req.Bytes())
case bytes.Buffer:
requestBody = bytes.NewBuffer(req.Bytes())
default:
jsonData, marshalErr := common.Marshal(convertedRequest)
if marshalErr != nil {
return testResult{
context: c,
localErr: err,
newAPIError: types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid),
localErr: marshalErr,
newAPIError: types.NewError(marshalErr, types.ErrorCodeJsonMarshalFailed),
}
}
//jsonData, err = relaycommon.RemoveDisabledFields(jsonData, info.ChannelOtherSettings)
//if err != nil {
// return testResult{
// context: c,
// localErr: err,
// newAPIError: types.NewError(err, types.ErrorCodeConvertRequestFailed),
// }
//}
if len(info.ParamOverride) > 0 {
jsonData, marshalErr = relaycommon.ApplyParamOverrideWithRelayInfo(jsonData, info)
if marshalErr != nil {
if fixedErr, ok := relaycommon.AsParamOverrideReturnError(marshalErr); ok {
return testResult{
context: c,
localErr: fixedErr,
newAPIError: relaycommon.NewAPIErrorFromParamOverride(fixedErr),
}
}
return testResult{
context: c,
localErr: marshalErr,
newAPIError: types.NewError(marshalErr, types.ErrorCodeChannelParamOverrideInvalid),
}
}
}
requestBody = bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))
}
requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(bytes.NewBuffer(jsonData))
resp, err := adaptor.DoRequest(c, info, requestBody)
if err != nil {
return testResult{
@@ -618,6 +693,14 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel,
N: lo.ToPtr(uint(1)),
Size: "1024x1024",
}
case constant.EndpointTypeImageEdit:
// 返回 ImageRequest
return &dto.ImageRequest{
Model: model,
Prompt: "edit this image",
N: lo.ToPtr(uint(1)),
Size: "1024x1024",
}
case constant.EndpointTypeJinaRerank:
// 返回 RerankRequest
return &dto.RerankRequest{