@@ -10,14 +10,13 @@ import (
1010 "fmt"
1111 "io"
1212 "path"
13- "strconv"
1413
1514 corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
1615 extprocv3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
1716 "github.com/tidwall/sjson"
1817
19- "github.com/envoyproxy/ai-gateway/internal/apischema/openai"
2018 tracing "github.com/envoyproxy/ai-gateway/internal/tracing/api"
19+ openaisdk "github.com/openai/openai-go/v2"
2120)
2221
2322// NewImageGenerationOpenAIToOpenAITranslator implements [Factory] for OpenAI to OpenAI image generation translation.
@@ -33,7 +32,7 @@ type openAIToOpenAIImageGenerationTranslator struct {
3332}
3433
3534// RequestBody implements [ImageGenerationTranslator.RequestBody].
36- func (o * openAIToOpenAIImageGenerationTranslator ) RequestBody (original []byte , req * openai. ImageGenerationRequest , forceBodyMutation bool ) (
35+ func (o * openAIToOpenAIImageGenerationTranslator ) RequestBody (original []byte , req * openaisdk. ImageGenerateParams , forceBodyMutation bool ) (
3736 headerMutation * extprocv3.HeaderMutation , bodyMutation * extprocv3.BodyMutation , err error ,
3837) {
3938 var newBody []byte
@@ -63,10 +62,8 @@ func (o *openAIToOpenAIImageGenerationTranslator) RequestBody(original []byte, r
6362 bodyMutation = & extprocv3.BodyMutation {
6463 Mutation : & extprocv3.BodyMutation_Body {Body : newBody },
6564 }
66- headerMutation .SetHeaders = append (headerMutation .SetHeaders , & corev3.HeaderValueOption {Header : & corev3.HeaderValue {
67- Key : "content-length" ,
68- RawValue : []byte (strconv .Itoa (len (newBody ))),
69- }})
65+ // Note: content-length header is set via dynamic metadata in the processor
66+ // to avoid conflicts with Envoy's REPLACE_AND_CONTINUE processing mode
7067 }
7168 return
7269}
@@ -79,12 +76,12 @@ func (o *openAIToOpenAIImageGenerationTranslator) ResponseError(respHeaders map[
7976) {
8077 statusCode := respHeaders [statusHeaderName ]
8178 if v , ok := respHeaders [contentTypeHeaderName ]; ok && v != jsonContentType {
82- var openaiError openai. ImageGenerationError
79+ var openaiError ImageGenerationError
8380 buf , err := io .ReadAll (body )
8481 if err != nil {
8582 return nil , nil , fmt .Errorf ("failed to read error body: %w" , err )
8683 }
87- openaiError = openai. ImageGenerationError {
84+ openaiError = ImageGenerationError {
8885 Error : struct {
8986 Type string `json:"type"`
9087 Message string `json:"message"`
@@ -102,6 +99,11 @@ func (o *openAIToOpenAIImageGenerationTranslator) ResponseError(respHeaders map[
10299 return nil , nil , fmt .Errorf ("failed to marshal error body: %w" , err )
103100 }
104101 headerMutation = & extprocv3.HeaderMutation {}
102+ // Ensure downstream sees a JSON error payload
103+ headerMutation .SetHeaders = append (headerMutation .SetHeaders , & corev3.HeaderValueOption {Header : & corev3.HeaderValue {
104+ Key : contentTypeHeaderName ,
105+ RawValue : []byte (jsonContentType ),
106+ }})
105107 setContentLength (headerMutation , mut .Body )
106108 return headerMutation , & extprocv3.BodyMutation {Mutation : mut }, nil
107109 }
@@ -117,22 +119,46 @@ func (o *openAIToOpenAIImageGenerationTranslator) ResponseHeaders(map[string]str
117119func (o * openAIToOpenAIImageGenerationTranslator ) ResponseBody (_ map [string ]string , body io.Reader , _ bool ) (
118120 headerMutation * extprocv3.HeaderMutation , bodyMutation * extprocv3.BodyMutation , tokenUsage LLMTokenUsage , imageMetadata ImageGenerationMetadata , err error ,
119121) {
120- resp := & openai.ImageGenerationResponse {}
121- if err := json .NewDecoder (body ).Decode (& resp ); err != nil {
122+ // Read the entire response body first to debug any issues
123+ bodyBytes , err := io .ReadAll (body )
124+ if err != nil {
125+ return nil , nil , tokenUsage , imageMetadata , fmt .Errorf ("failed to read response body: %w" , err )
126+ }
127+
128+ // Debug logging for response body content
129+ bodyPreview := string (bodyBytes )
130+ if len (bodyPreview ) > 200 {
131+ bodyPreview = bodyPreview [:200 ] + "..."
132+ }
133+ fmt .Printf ("DEBUG: Image generation translator received body - Length: %d, Preview: %s\n " , len (bodyBytes ), bodyPreview )
134+
135+ // Check if body looks like JSON
136+ if len (bodyBytes ) > 0 && bodyBytes [0 ] != '{' && bodyBytes [0 ] != '[' {
137+ previewLen := 10
138+ if len (bodyBytes ) < previewLen {
139+ previewLen = len (bodyBytes )
140+ }
141+ fmt .Printf ("DEBUG: Body does not start with JSON character. First %d bytes: %v\n " , previewLen , bodyBytes [:previewLen ])
142+ }
143+
144+ // Decode using OpenAI SDK v2 schema to avoid drift.
145+ resp := & openaisdk.ImagesResponse {}
146+ if err := json .Unmarshal (bodyBytes , & resp ); err != nil {
147+ fmt .Printf ("DEBUG: JSON unmarshal failed - Error: %v, Body preview: %s\n " , err , bodyPreview )
122148 return nil , nil , tokenUsage , imageMetadata , fmt .Errorf ("failed to unmarshal body: %w" , err )
123149 }
124150
125151 // Populate token usage if provided (GPT-Image-1); otherwise remain zero.
126- if resp .Usage != nil {
152+ if resp .JSON . Usage . Valid () {
127153 tokenUsage .InputTokens = uint32 (resp .Usage .InputTokens ) //nolint:gosec
128154 tokenUsage .OutputTokens = uint32 (resp .Usage .OutputTokens ) //nolint:gosec
129155 tokenUsage .TotalTokens = uint32 (resp .Usage .TotalTokens ) //nolint:gosec
130156 }
131157
132- // Extract image generation metadata for metrics
158+ // Extract image generation metadata for metrics (model may be absent in SDK response)
133159 imageMetadata .ImageCount = len (resp .Data )
134- imageMetadata .Model = resp . Model
135- imageMetadata .Size = resp .Size
160+ imageMetadata .Model = ""
161+ imageMetadata .Size = string ( resp .Size )
136162
137163 return
138164}
0 commit comments