fix: convert grok image edit uploads

This commit is contained in:
Heatherm Huang
2026-07-01 16:31:33 +08:00
parent f77cf6b477
commit aac3261c69
2 changed files with 120 additions and 0 deletions
+68
View File
@@ -292,6 +292,11 @@ func (s *OpenAIGatewayService) ForwardGrokMedia(
return nil, err
}
body, contentType, err = prepareGrokMediaForwardBody(endpoint, body, contentType)
if err != nil {
return nil, err
}
var bodyReader io.Reader
if endpoint.RequiresRequestBody() {
bodyReader = bytes.NewReader(body)
@@ -356,6 +361,69 @@ func (s *OpenAIGatewayService) ForwardGrokMedia(
}, nil
}
func prepareGrokMediaForwardBody(endpoint GrokMediaEndpoint, body []byte, contentType string) ([]byte, string, error) {
if endpoint != GrokMediaEndpointImagesEdits || gjson.ValidBytes(body) {
return body, contentType, nil
}
mediaType, _, err := mime.ParseMediaType(strings.TrimSpace(contentType))
if err != nil || !strings.EqualFold(mediaType, "multipart/form-data") {
return body, contentType, nil
}
info := ParseGrokMediaRequest(contentType, body)
payload := make(map[string]any)
if info.Model != "" {
payload["model"] = info.Model
}
if info.Prompt != "" {
payload["prompt"] = info.Prompt
}
if info.N > 1 {
payload["n"] = info.N
}
if info.Size != "" {
payload["size"] = info.Size
}
images := make([]map[string]string, 0, len(info.InputImageURLs)+len(info.Uploads))
for _, imageURL := range info.InputImageURLs {
if imageURL = strings.TrimSpace(imageURL); imageURL != "" {
images = append(images, map[string]string{"image_url": imageURL})
}
}
for _, upload := range info.Uploads {
dataURL, err := openAIImageUploadToDataURL(upload)
if err != nil {
return nil, "", err
}
images = append(images, map[string]string{"image_url": dataURL})
}
if len(images) > 0 {
payload["image"] = images[0]
if len(images) > 1 {
payload["images"] = images
}
}
maskImageURL := strings.TrimSpace(info.MaskImageURL)
if info.MaskUpload != nil {
dataURL, err := openAIImageUploadToDataURL(*info.MaskUpload)
if err != nil {
return nil, "", err
}
maskImageURL = dataURL
}
if maskImageURL != "" {
payload["mask"] = map[string]string{"image_url": maskImageURL}
}
out, err := marshalOpenAIUpstreamJSON(payload)
if err != nil {
return nil, "", err
}
return out, "application/json", nil
}
type grokMediaUsageMetadata struct {
ResponseID string
Usage OpenAIUsage
@@ -229,6 +229,58 @@ func TestForwardGrokMediaImagesGenerationPassthrough(t *testing.T) {
require.Equal(t, ImageBillingSize2K, result.ImageSize)
}
func TestForwardGrokMediaImagesEditMultipartConvertsToJSON(t *testing.T) {
t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
gin.SetMode(gin.TestMode)
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
require.NoError(t, writer.WriteField("model", "grok-imagine-edit"))
require.NoError(t, writer.WriteField("prompt", "edit this private image"))
partHeader := textproto.MIMEHeader{}
partHeader.Set("Content-Disposition", `form-data; name="image"; filename="input.png"`)
partHeader.Set("Content-Type", "image/png")
part, err := writer.CreatePart(partHeader)
require.NoError(t, err)
_, err = part.Write([]byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a})
require.NoError(t, err)
require.NoError(t, writer.Close())
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", bytes.NewReader(buf.Bytes()))
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
account := &Account{
ID: 62,
Name: "grok",
Platform: PlatformGrok,
Type: AccountTypeAPIKey,
Concurrency: 1,
Credentials: map[string]any{
"api_key": "api-key",
"base_url": "https://xai.test/v1",
},
}
upstream := &httpUpstreamRecorder{resp: &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{
"Content-Type": []string{"application/json"},
},
Body: io.NopCloser(strings.NewReader(`{"data":[]}`)),
}}
svc := &OpenAIGatewayService{httpUpstream: upstream}
_, err = svc.ForwardGrokMedia(context.Background(), c, account, GrokMediaEndpointImagesEdits, "", buf.Bytes(), writer.FormDataContentType())
require.NoError(t, err)
require.Equal(t, "https://xai.test/v1/images/edits", upstream.lastReq.URL.String())
require.Equal(t, "application/json", upstream.lastReq.Header.Get("Content-Type"))
require.True(t, json.Valid(upstream.lastBody))
require.Equal(t, "grok-imagine-edit", gjson.GetBytes(upstream.lastBody, "model").String())
require.Equal(t, "edit this private image", gjson.GetBytes(upstream.lastBody, "prompt").String())
require.True(t, strings.HasPrefix(gjson.GetBytes(upstream.lastBody, "image.image_url").String(), "data:image/png;base64,"))
}
func TestForwardGrokMediaVideoGenerationReturnsUsageAndResponseID(t *testing.T) {
t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
gin.SetMode(gin.TestMode)