Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
bf43fb4e
Unverified
Commit
bf43fb4e
authored
Apr 29, 2026
by
Wesley Liddick
Committed by
GitHub
Apr 29, 2026
Browse files
Merge pull request #2044 from VitalyAnkh/fix/openai-image-apikey-versioned-base-url
fix(openai): honor versioned image base URLs
parents
a16c6650
ca5d029e
Changes
3
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/account_test_service.go
View file @
bf43fb4e
...
...
@@ -1227,7 +1227,7 @@ func (s *AccountTestService) testOpenAIImageAPIKey(c *gin.Context, ctx context.C
if
err
!=
nil
{
return
s
.
sendErrorAndEnd
(
c
,
fmt
.
Sprintf
(
"Invalid base URL: %s"
,
err
.
Error
()))
}
apiURL
:=
strings
.
TrimSuffix
(
normalizedBaseURL
,
"/"
)
+
"/v1/i
mages
/g
enerations
"
apiURL
:=
buildOpenAIImagesURL
(
normalizedBaseURL
,
openAII
mages
G
enerations
Endpoint
)
// Set SSE headers
c
.
Writer
.
Header
()
.
Set
(
"Content-Type"
,
"text/event-stream"
)
...
...
backend/internal/service/account_test_service_openai_image_test.go
View file @
bf43fb4e
...
...
@@ -8,6 +8,7 @@ import (
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
...
...
@@ -48,3 +49,42 @@ func TestAccountTestService_OpenAIImageOAuthHandlesOutputItemDoneFallback(t *tes
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"data:image/png;base64,aGVsbG8="
)
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"
\"
success
\"
:true"
)
}
func
TestAccountTestService_OpenAIImageAPIKeyUsesConfiguredV1BaseURL
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/api/v1/admin/accounts/1/test"
,
nil
)
upstream
:=
&
httpUpstreamRecorder
{
resp
:
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Header
:
http
.
Header
{
"Content-Type"
:
[]
string
{
"application/json"
},
},
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
`{"data":[{"b64_json":"aGVsbG8=","revised_prompt":"draw a cat"}]}`
)),
},
}
svc
:=
&
AccountTestService
{
httpUpstream
:
upstream
,
cfg
:
&
config
.
Config
{},
}
account
:=
&
Account
{
ID
:
54
,
Name
:
"openai-apikey"
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeAPIKey
,
Credentials
:
map
[
string
]
any
{
"api_key"
:
"test-api-key"
,
"base_url"
:
"https://image-upstream.example/v1"
,
},
}
err
:=
svc
.
testOpenAIImageAPIKey
(
c
,
context
.
Background
(),
account
,
"gpt-image-2"
,
"draw a cat"
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
upstream
.
lastReq
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/generations"
,
upstream
.
lastReq
.
URL
.
String
())
require
.
Equal
(
t
,
"Bearer test-api-key"
,
upstream
.
lastReq
.
Header
.
Get
(
"Authorization"
))
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"data:image/png;base64,aGVsbG8="
)
require
.
Contains
(
t
,
rec
.
Body
.
String
(),
"
\"
success
\"
:true"
)
}
backend/internal/service/openai_images_test.go
View file @
bf43fb4e
...
...
@@ -11,6 +11,7 @@ import (
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
...
...
@@ -258,6 +259,25 @@ func TestAccountSupportsOpenAIImageCapability_OAuthSupportsNative(t *testing.T)
require
.
True
(
t
,
account
.
SupportsOpenAIImageCapability
(
OpenAIImagesCapabilityNative
))
}
func
TestBuildOpenAIImagesURL_HandlesVersionedBaseURL
(
t
*
testing
.
T
)
{
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/generations"
,
buildOpenAIImagesURL
(
"https://image-upstream.example/v1"
,
openAIImagesGenerationsEndpoint
),
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/edits"
,
buildOpenAIImagesURL
(
"https://image-upstream.example/v1/"
,
openAIImagesEditsEndpoint
),
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/generations"
,
buildOpenAIImagesURL
(
"https://image-upstream.example"
,
openAIImagesGenerationsEndpoint
),
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/generations"
,
buildOpenAIImagesURL
(
"https://image-upstream.example/v1/images/generations"
,
openAIImagesGenerationsEndpoint
),
)
}
type
openAIImageTestSSEEvent
struct
{
Name
string
Data
string
...
...
@@ -371,6 +391,124 @@ func TestOpenAIGatewayServiceForwardImages_OAuthUsesResponsesAPI(t *testing.T) {
require
.
Equal
(
t
,
"draw a cat"
,
gjson
.
Get
(
rec
.
Body
.
String
(),
"data.0.revised_prompt"
)
.
String
())
}
func
TestOpenAIGatewayServiceForwardImages_APIKeyGenerationUsesConfiguredV1BaseURL
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
body
:=
[]
byte
(
`{"model":"gpt-image-2","prompt":"draw a cat","response_format":"b64_json"}`
)
req
:=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/images/generations"
,
bytes
.
NewReader
(
body
))
req
.
Header
.
Set
(
"Content-Type"
,
"application/json"
)
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
req
svc
:=
&
OpenAIGatewayService
{
cfg
:
&
config
.
Config
{},
httpUpstream
:
&
httpUpstreamRecorder
{
resp
:
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Header
:
http
.
Header
{
"Content-Type"
:
[]
string
{
"application/json"
},
"X-Request-Id"
:
[]
string
{
"req_img_apikey"
},
},
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
`{"created":1710000007,"data":[{"b64_json":"aGVsbG8=","revised_prompt":"draw a cat"}]}`
)),
},
},
}
parsed
,
err
:=
svc
.
ParseOpenAIImagesRequest
(
c
,
body
)
require
.
NoError
(
t
,
err
)
account
:=
&
Account
{
ID
:
6
,
Name
:
"openai-apikey"
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeAPIKey
,
Credentials
:
map
[
string
]
any
{
"api_key"
:
"test-api-key"
,
"base_url"
:
"https://image-upstream.example/v1"
,
},
}
result
,
err
:=
svc
.
ForwardImages
(
context
.
Background
(),
c
,
account
,
body
,
parsed
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
Equal
(
t
,
1
,
result
.
ImageCount
)
require
.
Equal
(
t
,
"gpt-image-2"
,
result
.
Model
)
require
.
Equal
(
t
,
"gpt-image-2"
,
result
.
UpstreamModel
)
upstream
,
ok
:=
svc
.
httpUpstream
.
(
*
httpUpstreamRecorder
)
require
.
True
(
t
,
ok
)
require
.
NotNil
(
t
,
upstream
.
lastReq
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/generations"
,
upstream
.
lastReq
.
URL
.
String
())
require
.
Equal
(
t
,
"Bearer test-api-key"
,
upstream
.
lastReq
.
Header
.
Get
(
"Authorization"
))
require
.
Equal
(
t
,
"application/json"
,
upstream
.
lastReq
.
Header
.
Get
(
"Content-Type"
))
require
.
Equal
(
t
,
"gpt-image-2"
,
gjson
.
GetBytes
(
upstream
.
lastBody
,
"model"
)
.
String
())
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"aGVsbG8="
,
gjson
.
Get
(
rec
.
Body
.
String
(),
"data.0.b64_json"
)
.
String
())
}
func
TestOpenAIGatewayServiceForwardImages_APIKeyEditUsesConfiguredV1BaseURL
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
var
body
bytes
.
Buffer
writer
:=
multipart
.
NewWriter
(
&
body
)
require
.
NoError
(
t
,
writer
.
WriteField
(
"model"
,
"gpt-image-2"
))
require
.
NoError
(
t
,
writer
.
WriteField
(
"prompt"
,
"replace background"
))
imagePart
,
err
:=
writer
.
CreateFormFile
(
"image"
,
"source.png"
)
require
.
NoError
(
t
,
err
)
_
,
err
=
imagePart
.
Write
([]
byte
(
"png-image-content"
))
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
writer
.
Close
())
req
:=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/images/edits"
,
bytes
.
NewReader
(
body
.
Bytes
()))
req
.
Header
.
Set
(
"Content-Type"
,
writer
.
FormDataContentType
())
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
req
svc
:=
&
OpenAIGatewayService
{
cfg
:
&
config
.
Config
{},
httpUpstream
:
&
httpUpstreamRecorder
{
resp
:
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Header
:
http
.
Header
{
"Content-Type"
:
[]
string
{
"application/json"
},
"X-Request-Id"
:
[]
string
{
"req_img_edit_apikey"
},
},
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
`{"created":1710000008,"data":[{"b64_json":"ZWRpdGVk","revised_prompt":"replace background"}]}`
)),
},
},
}
parsed
,
err
:=
svc
.
ParseOpenAIImagesRequest
(
c
,
body
.
Bytes
())
require
.
NoError
(
t
,
err
)
account
:=
&
Account
{
ID
:
7
,
Name
:
"openai-apikey"
,
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeAPIKey
,
Credentials
:
map
[
string
]
any
{
"api_key"
:
"test-api-key"
,
"base_url"
:
"https://image-upstream.example/v1/"
,
},
}
result
,
err
:=
svc
.
ForwardImages
(
context
.
Background
(),
c
,
account
,
body
.
Bytes
(),
parsed
,
""
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
Equal
(
t
,
1
,
result
.
ImageCount
)
upstream
,
ok
:=
svc
.
httpUpstream
.
(
*
httpUpstreamRecorder
)
require
.
True
(
t
,
ok
)
require
.
NotNil
(
t
,
upstream
.
lastReq
)
require
.
Equal
(
t
,
"https://image-upstream.example/v1/images/edits"
,
upstream
.
lastReq
.
URL
.
String
())
require
.
Equal
(
t
,
"Bearer test-api-key"
,
upstream
.
lastReq
.
Header
.
Get
(
"Authorization"
))
require
.
Contains
(
t
,
upstream
.
lastReq
.
Header
.
Get
(
"Content-Type"
),
"multipart/form-data"
)
require
.
Contains
(
t
,
string
(
upstream
.
lastBody
),
`name="model"`
)
require
.
Contains
(
t
,
string
(
upstream
.
lastBody
),
"gpt-image-2"
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"ZWRpdGVk"
,
gjson
.
Get
(
rec
.
Body
.
String
(),
"data.0.b64_json"
)
.
String
())
}
func
TestOpenAIGatewayServiceForwardImages_OAuthStreamingTransformsEvents
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
body
:=
[]
byte
(
`{"model":"gpt-image-2","prompt":"draw a cat","stream":true,"response_format":"url"}`
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment