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
995ef134
Commit
995ef134
authored
Mar 24, 2026
by
InCerry
Browse files
refactor: improve model resolution and normalization logic for OpenAI integration
parent
08c4e514
Changes
10
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/openai_codex_transform.go
View file @
995ef134
...
@@ -85,7 +85,7 @@ func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool, isCompact
...
@@ -85,7 +85,7 @@ func applyCodexOAuthTransform(reqBody map[string]any, isCodexCLI bool, isCompact
if
v
,
ok
:=
reqBody
[
"model"
]
.
(
string
);
ok
{
if
v
,
ok
:=
reqBody
[
"model"
]
.
(
string
);
ok
{
model
=
v
model
=
v
}
}
normalizedModel
:=
normalizeCodexModel
(
model
)
normalizedModel
:=
strings
.
TrimSpace
(
model
)
if
normalizedModel
!=
""
{
if
normalizedModel
!=
""
{
if
model
!=
normalizedModel
{
if
model
!=
normalizedModel
{
reqBody
[
"model"
]
=
normalizedModel
reqBody
[
"model"
]
=
normalizedModel
...
...
backend/internal/service/openai_codex_transform_test.go
View file @
995ef134
...
@@ -246,6 +246,7 @@ func TestNormalizeCodexModel_Gpt53(t *testing.T) {
...
@@ -246,6 +246,7 @@ func TestNormalizeCodexModel_Gpt53(t *testing.T) {
"gpt-5.3-codex"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-xhigh"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-xhigh"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark"
:
"gpt-5.3-codex"
,
"gpt 5.3 codex spark"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark-high"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark-high"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark-xhigh"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark-xhigh"
:
"gpt-5.3-codex"
,
"gpt 5.3 codex"
:
"gpt-5.3-codex"
,
"gpt 5.3 codex"
:
"gpt-5.3-codex"
,
...
@@ -256,6 +257,34 @@ func TestNormalizeCodexModel_Gpt53(t *testing.T) {
...
@@ -256,6 +257,34 @@ func TestNormalizeCodexModel_Gpt53(t *testing.T) {
}
}
}
}
func
TestApplyCodexOAuthTransform_PreservesBareSparkModel
(
t
*
testing
.
T
)
{
reqBody
:=
map
[
string
]
any
{
"model"
:
"gpt-5.3-codex-spark"
,
"input"
:
[]
any
{},
}
result
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
require
.
Equal
(
t
,
"gpt-5.3-codex-spark"
,
reqBody
[
"model"
])
require
.
Equal
(
t
,
"gpt-5.3-codex-spark"
,
result
.
NormalizedModel
)
store
,
ok
:=
reqBody
[
"store"
]
.
(
bool
)
require
.
True
(
t
,
ok
)
require
.
False
(
t
,
store
)
}
func
TestApplyCodexOAuthTransform_TrimmedModelWithoutPolicyRewrite
(
t
*
testing
.
T
)
{
reqBody
:=
map
[
string
]
any
{
"model"
:
" gpt-5.3-codex-spark "
,
"input"
:
[]
any
{},
}
result
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
require
.
Equal
(
t
,
"gpt-5.3-codex-spark"
,
reqBody
[
"model"
])
require
.
Equal
(
t
,
"gpt-5.3-codex-spark"
,
result
.
NormalizedModel
)
require
.
True
(
t
,
result
.
Modified
)
}
func
TestApplyCodexOAuthTransform_CodexCLI_PreservesExistingInstructions
(
t
*
testing
.
T
)
{
func
TestApplyCodexOAuthTransform_CodexCLI_PreservesExistingInstructions
(
t
*
testing
.
T
)
{
// Codex CLI 场景:已有 instructions 时不修改
// Codex CLI 场景:已有 instructions 时不修改
...
...
backend/internal/service/openai_compat_prompt_cache_key.go
View file @
995ef134
...
@@ -10,8 +10,8 @@ import (
...
@@ -10,8 +10,8 @@ import (
const
compatPromptCacheKeyPrefix
=
"compat_cc_"
const
compatPromptCacheKeyPrefix
=
"compat_cc_"
func
shouldAutoInjectPromptCacheKeyForCompat
(
model
string
)
bool
{
func
shouldAutoInjectPromptCacheKeyForCompat
(
model
string
)
bool
{
switch
normalizeCodex
Model
(
strings
.
TrimSpace
(
model
))
{
switch
resolveOpenAIUpstream
Model
(
strings
.
TrimSpace
(
model
))
{
case
"gpt-5.4"
,
"gpt-5.3-codex"
:
case
"gpt-5.4"
,
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark"
:
return
true
return
true
default
:
default
:
return
false
return
false
...
@@ -23,9 +23,9 @@ func deriveCompatPromptCacheKey(req *apicompat.ChatCompletionsRequest, mappedMod
...
@@ -23,9 +23,9 @@ func deriveCompatPromptCacheKey(req *apicompat.ChatCompletionsRequest, mappedMod
return
""
return
""
}
}
normalizedModel
:=
normalizeCodex
Model
(
strings
.
TrimSpace
(
mappedModel
))
normalizedModel
:=
resolveOpenAIUpstream
Model
(
strings
.
TrimSpace
(
mappedModel
))
if
normalizedModel
==
""
{
if
normalizedModel
==
""
{
normalizedModel
=
normalizeCodex
Model
(
strings
.
TrimSpace
(
req
.
Model
))
normalizedModel
=
resolveOpenAIUpstream
Model
(
strings
.
TrimSpace
(
req
.
Model
))
}
}
if
normalizedModel
==
""
{
if
normalizedModel
==
""
{
normalizedModel
=
strings
.
TrimSpace
(
req
.
Model
)
normalizedModel
=
strings
.
TrimSpace
(
req
.
Model
)
...
...
backend/internal/service/openai_compat_prompt_cache_key_test.go
View file @
995ef134
...
@@ -17,6 +17,7 @@ func TestShouldAutoInjectPromptCacheKeyForCompat(t *testing.T) {
...
@@ -17,6 +17,7 @@ func TestShouldAutoInjectPromptCacheKeyForCompat(t *testing.T) {
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.4"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.4"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.3"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.3"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.3-codex"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.3-codex"
))
require
.
True
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-5.3-codex-spark"
))
require
.
False
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-4o"
))
require
.
False
(
t
,
shouldAutoInjectPromptCacheKeyForCompat
(
"gpt-4o"
))
}
}
...
@@ -62,3 +63,17 @@ func TestDeriveCompatPromptCacheKey_DiffersAcrossSessions(t *testing.T) {
...
@@ -62,3 +63,17 @@ func TestDeriveCompatPromptCacheKey_DiffersAcrossSessions(t *testing.T) {
k2
:=
deriveCompatPromptCacheKey
(
req2
,
"gpt-5.4"
)
k2
:=
deriveCompatPromptCacheKey
(
req2
,
"gpt-5.4"
)
require
.
NotEqual
(
t
,
k1
,
k2
,
"different first user messages should yield different keys"
)
require
.
NotEqual
(
t
,
k1
,
k2
,
"different first user messages should yield different keys"
)
}
}
func
TestDeriveCompatPromptCacheKey_UsesResolvedSparkFamily
(
t
*
testing
.
T
)
{
req
:=
&
apicompat
.
ChatCompletionsRequest
{
Model
:
"gpt-5.3-codex-spark"
,
Messages
:
[]
apicompat
.
ChatMessage
{
{
Role
:
"user"
,
Content
:
mustRawJSON
(
t
,
`"Question A"`
)},
},
}
k1
:=
deriveCompatPromptCacheKey
(
req
,
"gpt-5.3-codex-spark"
)
k2
:=
deriveCompatPromptCacheKey
(
req
,
" openai/gpt-5.3-codex-spark "
)
require
.
NotEmpty
(
t
,
k1
)
require
.
Equal
(
t
,
k1
,
k2
,
"resolved spark family should derive a stable compat cache key"
)
}
backend/internal/service/openai_gateway_chat_completions.go
View file @
995ef134
...
@@ -45,12 +45,13 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
...
@@ -45,12 +45,13 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
// 2. Resolve model mapping early so compat prompt_cache_key injection can
// 2. Resolve model mapping early so compat prompt_cache_key injection can
// derive a stable seed from the final upstream model family.
// derive a stable seed from the final upstream model family.
mappedModel
:=
resolveOpenAIForwardModel
(
account
,
originalModel
,
defaultMappedModel
)
billingModel
:=
resolveOpenAIForwardModel
(
account
,
originalModel
,
defaultMappedModel
)
upstreamModel
:=
resolveOpenAIUpstreamModel
(
billingModel
)
promptCacheKey
=
strings
.
TrimSpace
(
promptCacheKey
)
promptCacheKey
=
strings
.
TrimSpace
(
promptCacheKey
)
compatPromptCacheInjected
:=
false
compatPromptCacheInjected
:=
false
if
promptCacheKey
==
""
&&
account
.
Type
==
AccountTypeOAuth
&&
shouldAutoInjectPromptCacheKeyForCompat
(
mapped
Model
)
{
if
promptCacheKey
==
""
&&
account
.
Type
==
AccountTypeOAuth
&&
shouldAutoInjectPromptCacheKeyForCompat
(
upstream
Model
)
{
promptCacheKey
=
deriveCompatPromptCacheKey
(
&
chatReq
,
mapped
Model
)
promptCacheKey
=
deriveCompatPromptCacheKey
(
&
chatReq
,
upstream
Model
)
compatPromptCacheInjected
=
promptCacheKey
!=
""
compatPromptCacheInjected
=
promptCacheKey
!=
""
}
}
...
@@ -60,12 +61,13 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
...
@@ -60,12 +61,13 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"convert chat completions to responses: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"convert chat completions to responses: %w"
,
err
)
}
}
responsesReq
.
Model
=
mapped
Model
responsesReq
.
Model
=
upstream
Model
logFields
:=
[]
zap
.
Field
{
logFields
:=
[]
zap
.
Field
{
zap
.
Int64
(
"account_id"
,
account
.
ID
),
zap
.
Int64
(
"account_id"
,
account
.
ID
),
zap
.
String
(
"original_model"
,
originalModel
),
zap
.
String
(
"original_model"
,
originalModel
),
zap
.
String
(
"mapped_model"
,
mappedModel
),
zap
.
String
(
"billing_model"
,
billingModel
),
zap
.
String
(
"upstream_model"
,
upstreamModel
),
zap
.
Bool
(
"stream"
,
clientStream
),
zap
.
Bool
(
"stream"
,
clientStream
),
}
}
if
compatPromptCacheInjected
{
if
compatPromptCacheInjected
{
...
@@ -88,6 +90,9 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
...
@@ -88,6 +90,9 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
return
nil
,
fmt
.
Errorf
(
"unmarshal for codex transform: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"unmarshal for codex transform: %w"
,
err
)
}
}
codexResult
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
codexResult
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
if
codexResult
.
NormalizedModel
!=
""
{
upstreamModel
=
codexResult
.
NormalizedModel
}
if
codexResult
.
PromptCacheKey
!=
""
{
if
codexResult
.
PromptCacheKey
!=
""
{
promptCacheKey
=
codexResult
.
PromptCacheKey
promptCacheKey
=
codexResult
.
PromptCacheKey
}
else
if
promptCacheKey
!=
""
{
}
else
if
promptCacheKey
!=
""
{
...
@@ -180,9 +185,9 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
...
@@ -180,9 +185,9 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
var
result
*
OpenAIForwardResult
var
result
*
OpenAIForwardResult
var
handleErr
error
var
handleErr
error
if
clientStream
{
if
clientStream
{
result
,
handleErr
=
s
.
handleChatStreamingResponse
(
resp
,
c
,
originalModel
,
mapped
Model
,
includeUsage
,
startTime
)
result
,
handleErr
=
s
.
handleChatStreamingResponse
(
resp
,
c
,
originalModel
,
billingModel
,
upstream
Model
,
includeUsage
,
startTime
)
}
else
{
}
else
{
result
,
handleErr
=
s
.
handleChatBufferedStreamingResponse
(
resp
,
c
,
originalModel
,
mapped
Model
,
startTime
)
result
,
handleErr
=
s
.
handleChatBufferedStreamingResponse
(
resp
,
c
,
originalModel
,
billingModel
,
upstream
Model
,
startTime
)
}
}
// Propagate ServiceTier and ReasoningEffort to result for billing
// Propagate ServiceTier and ReasoningEffort to result for billing
...
@@ -224,7 +229,8 @@ func (s *OpenAIGatewayService) handleChatBufferedStreamingResponse(
...
@@ -224,7 +229,8 @@ func (s *OpenAIGatewayService) handleChatBufferedStreamingResponse(
resp
*
http
.
Response
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
c
*
gin
.
Context
,
originalModel
string
,
originalModel
string
,
mappedModel
string
,
billingModel
string
,
upstreamModel
string
,
startTime
time
.
Time
,
startTime
time
.
Time
,
)
(
*
OpenAIForwardResult
,
error
)
{
)
(
*
OpenAIForwardResult
,
error
)
{
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
...
@@ -295,8 +301,8 @@ func (s *OpenAIGatewayService) handleChatBufferedStreamingResponse(
...
@@ -295,8 +301,8 @@ func (s *OpenAIGatewayService) handleChatBufferedStreamingResponse(
RequestID
:
requestID
,
RequestID
:
requestID
,
Usage
:
usage
,
Usage
:
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
BillingModel
:
mapped
Model
,
BillingModel
:
billing
Model
,
UpstreamModel
:
mapped
Model
,
UpstreamModel
:
upstream
Model
,
Stream
:
false
,
Stream
:
false
,
Duration
:
time
.
Since
(
startTime
),
Duration
:
time
.
Since
(
startTime
),
},
nil
},
nil
...
@@ -308,7 +314,8 @@ func (s *OpenAIGatewayService) handleChatStreamingResponse(
...
@@ -308,7 +314,8 @@ func (s *OpenAIGatewayService) handleChatStreamingResponse(
resp
*
http
.
Response
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
c
*
gin
.
Context
,
originalModel
string
,
originalModel
string
,
mappedModel
string
,
billingModel
string
,
upstreamModel
string
,
includeUsage
bool
,
includeUsage
bool
,
startTime
time
.
Time
,
startTime
time
.
Time
,
)
(
*
OpenAIForwardResult
,
error
)
{
)
(
*
OpenAIForwardResult
,
error
)
{
...
@@ -343,8 +350,8 @@ func (s *OpenAIGatewayService) handleChatStreamingResponse(
...
@@ -343,8 +350,8 @@ func (s *OpenAIGatewayService) handleChatStreamingResponse(
RequestID
:
requestID
,
RequestID
:
requestID
,
Usage
:
usage
,
Usage
:
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
BillingModel
:
mapped
Model
,
BillingModel
:
billing
Model
,
UpstreamModel
:
mapped
Model
,
UpstreamModel
:
upstream
Model
,
Stream
:
true
,
Stream
:
true
,
Duration
:
time
.
Since
(
startTime
),
Duration
:
time
.
Since
(
startTime
),
FirstTokenMs
:
firstTokenMs
,
FirstTokenMs
:
firstTokenMs
,
...
...
backend/internal/service/openai_gateway_messages.go
View file @
995ef134
...
@@ -59,13 +59,15 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
...
@@ -59,13 +59,15 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
}
}
// 3. Model mapping
// 3. Model mapping
mappedModel
:=
resolveOpenAIForwardModel
(
account
,
originalModel
,
defaultMappedModel
)
billingModel
:=
resolveOpenAIForwardModel
(
account
,
originalModel
,
defaultMappedModel
)
responsesReq
.
Model
=
mappedModel
upstreamModel
:=
resolveOpenAIUpstreamModel
(
billingModel
)
responsesReq
.
Model
=
upstreamModel
logger
.
L
()
.
Debug
(
"openai messages: model mapping applied"
,
logger
.
L
()
.
Debug
(
"openai messages: model mapping applied"
,
zap
.
Int64
(
"account_id"
,
account
.
ID
),
zap
.
Int64
(
"account_id"
,
account
.
ID
),
zap
.
String
(
"original_model"
,
originalModel
),
zap
.
String
(
"original_model"
,
originalModel
),
zap
.
String
(
"mapped_model"
,
mappedModel
),
zap
.
String
(
"billing_model"
,
billingModel
),
zap
.
String
(
"upstream_model"
,
upstreamModel
),
zap
.
Bool
(
"stream"
,
isStream
),
zap
.
Bool
(
"stream"
,
isStream
),
)
)
...
@@ -81,6 +83,9 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
...
@@ -81,6 +83,9 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
return
nil
,
fmt
.
Errorf
(
"unmarshal for codex transform: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"unmarshal for codex transform: %w"
,
err
)
}
}
codexResult
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
codexResult
:=
applyCodexOAuthTransform
(
reqBody
,
false
,
false
)
if
codexResult
.
NormalizedModel
!=
""
{
upstreamModel
=
codexResult
.
NormalizedModel
}
if
codexResult
.
PromptCacheKey
!=
""
{
if
codexResult
.
PromptCacheKey
!=
""
{
promptCacheKey
=
codexResult
.
PromptCacheKey
promptCacheKey
=
codexResult
.
PromptCacheKey
}
else
if
promptCacheKey
!=
""
{
}
else
if
promptCacheKey
!=
""
{
...
@@ -181,10 +186,10 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
...
@@ -181,10 +186,10 @@ func (s *OpenAIGatewayService) ForwardAsAnthropic(
var
result
*
OpenAIForwardResult
var
result
*
OpenAIForwardResult
var
handleErr
error
var
handleErr
error
if
clientStream
{
if
clientStream
{
result
,
handleErr
=
s
.
handleAnthropicStreamingResponse
(
resp
,
c
,
originalModel
,
mapped
Model
,
startTime
)
result
,
handleErr
=
s
.
handleAnthropicStreamingResponse
(
resp
,
c
,
originalModel
,
billingModel
,
upstream
Model
,
startTime
)
}
else
{
}
else
{
// Client wants JSON: buffer the streaming response and assemble a JSON reply.
// Client wants JSON: buffer the streaming response and assemble a JSON reply.
result
,
handleErr
=
s
.
handleAnthropicBufferedStreamingResponse
(
resp
,
c
,
originalModel
,
mapped
Model
,
startTime
)
result
,
handleErr
=
s
.
handleAnthropicBufferedStreamingResponse
(
resp
,
c
,
originalModel
,
billingModel
,
upstream
Model
,
startTime
)
}
}
// Propagate ServiceTier and ReasoningEffort to result for billing
// Propagate ServiceTier and ReasoningEffort to result for billing
...
@@ -229,7 +234,8 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
...
@@ -229,7 +234,8 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
resp
*
http
.
Response
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
c
*
gin
.
Context
,
originalModel
string
,
originalModel
string
,
mappedModel
string
,
billingModel
string
,
upstreamModel
string
,
startTime
time
.
Time
,
startTime
time
.
Time
,
)
(
*
OpenAIForwardResult
,
error
)
{
)
(
*
OpenAIForwardResult
,
error
)
{
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
...
@@ -302,8 +308,8 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
...
@@ -302,8 +308,8 @@ func (s *OpenAIGatewayService) handleAnthropicBufferedStreamingResponse(
RequestID
:
requestID
,
RequestID
:
requestID
,
Usage
:
usage
,
Usage
:
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
BillingModel
:
mapped
Model
,
BillingModel
:
billing
Model
,
UpstreamModel
:
mapped
Model
,
UpstreamModel
:
upstream
Model
,
Stream
:
false
,
Stream
:
false
,
Duration
:
time
.
Since
(
startTime
),
Duration
:
time
.
Since
(
startTime
),
},
nil
},
nil
...
@@ -318,7 +324,8 @@ func (s *OpenAIGatewayService) handleAnthropicStreamingResponse(
...
@@ -318,7 +324,8 @@ func (s *OpenAIGatewayService) handleAnthropicStreamingResponse(
resp
*
http
.
Response
,
resp
*
http
.
Response
,
c
*
gin
.
Context
,
c
*
gin
.
Context
,
originalModel
string
,
originalModel
string
,
mappedModel
string
,
billingModel
string
,
upstreamModel
string
,
startTime
time
.
Time
,
startTime
time
.
Time
,
)
(
*
OpenAIForwardResult
,
error
)
{
)
(
*
OpenAIForwardResult
,
error
)
{
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
requestID
:=
resp
.
Header
.
Get
(
"x-request-id"
)
...
@@ -351,8 +358,8 @@ func (s *OpenAIGatewayService) handleAnthropicStreamingResponse(
...
@@ -351,8 +358,8 @@ func (s *OpenAIGatewayService) handleAnthropicStreamingResponse(
RequestID
:
requestID
,
RequestID
:
requestID
,
Usage
:
usage
,
Usage
:
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
BillingModel
:
mapped
Model
,
BillingModel
:
billing
Model
,
UpstreamModel
:
mapped
Model
,
UpstreamModel
:
upstream
Model
,
Stream
:
true
,
Stream
:
true
,
Duration
:
time
.
Since
(
startTime
),
Duration
:
time
.
Since
(
startTime
),
FirstTokenMs
:
firstTokenMs
,
FirstTokenMs
:
firstTokenMs
,
...
...
backend/internal/service/openai_gateway_service.go
View file @
995ef134
...
@@ -1778,29 +1778,29 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -1778,29 +1778,29 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
}
}
// 对所有请求执行模型映射(包含 Codex CLI)。
// 对所有请求执行模型映射(包含 Codex CLI)。
mapped
Model
:=
account
.
GetMappedModel
(
reqModel
)
billing
Model
:=
account
.
GetMappedModel
(
reqModel
)
if
mapped
Model
!=
reqModel
{
if
billing
Model
!=
reqModel
{
logger
.
LegacyPrintf
(
"service.openai_gateway"
,
"[OpenAI] Model mapping applied: %s -> %s (account: %s, isCodexCLI: %v)"
,
reqModel
,
mapped
Model
,
account
.
Name
,
isCodexCLI
)
logger
.
LegacyPrintf
(
"service.openai_gateway"
,
"[OpenAI] Model mapping applied: %s -> %s (account: %s, isCodexCLI: %v)"
,
reqModel
,
billing
Model
,
account
.
Name
,
isCodexCLI
)
reqBody
[
"model"
]
=
mapped
Model
reqBody
[
"model"
]
=
billing
Model
bodyModified
=
true
bodyModified
=
true
markPatchSet
(
"model"
,
mapped
Model
)
markPatchSet
(
"model"
,
billing
Model
)
}
}
upstreamModel
:=
billingModel
// 针对所有 OpenAI 账号执行 Codex 模型名规范化,确保上游识别一致。
// 针对所有 OpenAI 账号执行 Codex 模型名规范化,确保上游识别一致。
if
model
,
ok
:=
reqBody
[
"model"
]
.
(
string
);
ok
{
if
model
,
ok
:=
reqBody
[
"model"
]
.
(
string
);
ok
{
normalizedModel
:=
normalizeCodexModel
(
model
)
upstreamModel
=
resolveOpenAIUpstreamModel
(
model
)
if
normalizedModel
!=
""
&&
normalizedModel
!=
model
{
if
upstreamModel
!=
""
&&
upstreamModel
!=
model
{
logger
.
LegacyPrintf
(
"service.openai_gateway"
,
"[OpenAI] Codex model normalization: %s -> %s (account: %s, type: %s, isCodexCLI: %v)"
,
logger
.
LegacyPrintf
(
"service.openai_gateway"
,
"[OpenAI] Upstream model resolved: %s -> %s (account: %s, type: %s, isCodexCLI: %v)"
,
model
,
normalizedModel
,
account
.
Name
,
account
.
Type
,
isCodexCLI
)
model
,
upstreamModel
,
account
.
Name
,
account
.
Type
,
isCodexCLI
)
reqBody
[
"model"
]
=
normalizedModel
reqBody
[
"model"
]
=
upstreamModel
mappedModel
=
normalizedModel
bodyModified
=
true
bodyModified
=
true
markPatchSet
(
"model"
,
normalized
Model
)
markPatchSet
(
"model"
,
upstream
Model
)
}
}
// 移除 gpt-5.2-codex 以下的版本 verbosity 参数
// 移除 gpt-5.2-codex 以下的版本 verbosity 参数
// 确保高版本模型向低版本模型映射不报错
// 确保高版本模型向低版本模型映射不报错
if
!
SupportsVerbosity
(
normalized
Model
)
{
if
!
SupportsVerbosity
(
upstream
Model
)
{
if
text
,
ok
:=
reqBody
[
"text"
]
.
(
map
[
string
]
any
);
ok
{
if
text
,
ok
:=
reqBody
[
"text"
]
.
(
map
[
string
]
any
);
ok
{
delete
(
text
,
"verbosity"
)
delete
(
text
,
"verbosity"
)
}
}
...
@@ -1824,7 +1824,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -1824,7 +1824,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
disablePatch
()
disablePatch
()
}
}
if
codexResult
.
NormalizedModel
!=
""
{
if
codexResult
.
NormalizedModel
!=
""
{
mapped
Model
=
codexResult
.
NormalizedModel
upstream
Model
=
codexResult
.
NormalizedModel
}
}
if
codexResult
.
PromptCacheKey
!=
""
{
if
codexResult
.
PromptCacheKey
!=
""
{
promptCacheKey
=
codexResult
.
PromptCacheKey
promptCacheKey
=
codexResult
.
PromptCacheKey
...
@@ -1941,7 +1941,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -1941,7 +1941,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
"forward_start account_id=%d account_type=%s model=%s stream=%v has_previous_response_id=%v"
,
"forward_start account_id=%d account_type=%s model=%s stream=%v has_previous_response_id=%v"
,
account
.
ID
,
account
.
ID
,
account
.
Type
,
account
.
Type
,
mapped
Model
,
upstream
Model
,
reqStream
,
reqStream
,
hasPreviousResponseID
,
hasPreviousResponseID
,
)
)
...
@@ -2030,7 +2030,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -2030,7 +2030,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
isCodexCLI
,
isCodexCLI
,
reqStream
,
reqStream
,
originalModel
,
originalModel
,
mapped
Model
,
upstream
Model
,
startTime
,
startTime
,
attempt
,
attempt
,
wsLastFailureReason
,
wsLastFailureReason
,
...
@@ -2131,7 +2131,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -2131,7 +2131,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
firstTokenMs
,
firstTokenMs
,
wsAttempts
,
wsAttempts
,
)
)
wsResult
.
UpstreamModel
=
mapped
Model
wsResult
.
UpstreamModel
=
upstream
Model
return
wsResult
,
nil
return
wsResult
,
nil
}
}
s
.
writeOpenAIWSFallbackErrorResponse
(
c
,
account
,
wsErr
)
s
.
writeOpenAIWSFallbackErrorResponse
(
c
,
account
,
wsErr
)
...
@@ -2236,14 +2236,14 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -2236,14 +2236,14 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
var
usage
*
OpenAIUsage
var
usage
*
OpenAIUsage
var
firstTokenMs
*
int
var
firstTokenMs
*
int
if
reqStream
{
if
reqStream
{
streamResult
,
err
:=
s
.
handleStreamingResponse
(
ctx
,
resp
,
c
,
account
,
startTime
,
originalModel
,
mapped
Model
)
streamResult
,
err
:=
s
.
handleStreamingResponse
(
ctx
,
resp
,
c
,
account
,
startTime
,
originalModel
,
upstream
Model
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
usage
=
streamResult
.
usage
usage
=
streamResult
.
usage
firstTokenMs
=
streamResult
.
firstTokenMs
firstTokenMs
=
streamResult
.
firstTokenMs
}
else
{
}
else
{
usage
,
err
=
s
.
handleNonStreamingResponse
(
ctx
,
resp
,
c
,
account
,
originalModel
,
mapped
Model
)
usage
,
err
=
s
.
handleNonStreamingResponse
(
ctx
,
resp
,
c
,
account
,
originalModel
,
upstream
Model
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
@@ -2267,7 +2267,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
...
@@ -2267,7 +2267,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
RequestID
:
resp
.
Header
.
Get
(
"x-request-id"
),
RequestID
:
resp
.
Header
.
Get
(
"x-request-id"
),
Usage
:
*
usage
,
Usage
:
*
usage
,
Model
:
originalModel
,
Model
:
originalModel
,
UpstreamModel
:
mapped
Model
,
UpstreamModel
:
upstream
Model
,
ServiceTier
:
serviceTier
,
ServiceTier
:
serviceTier
,
ReasoningEffort
:
reasoningEffort
,
ReasoningEffort
:
reasoningEffort
,
Stream
:
reqStream
,
Stream
:
reqStream
,
...
...
backend/internal/service/openai_model_mapping.go
View file @
995ef134
package
service
package
service
// resolveOpenAIForwardModel determines the upstream model for OpenAI-compatible
import
"strings"
// forwarding. Group-level default mapping only applies when the account itself
// did not match any explicit model_mapping rule.
// resolveOpenAIForwardModel resolves the account/group mapping result for
// OpenAI-compatible forwarding. Group-level default mapping only applies when
// the account itself did not match any explicit model_mapping rule.
func
resolveOpenAIForwardModel
(
account
*
Account
,
requestedModel
,
defaultMappedModel
string
)
string
{
func
resolveOpenAIForwardModel
(
account
*
Account
,
requestedModel
,
defaultMappedModel
string
)
string
{
if
account
==
nil
{
if
account
==
nil
{
if
defaultMappedModel
!=
""
{
if
defaultMappedModel
!=
""
{
...
@@ -17,3 +19,23 @@ func resolveOpenAIForwardModel(account *Account, requestedModel, defaultMappedMo
...
@@ -17,3 +19,23 @@ func resolveOpenAIForwardModel(account *Account, requestedModel, defaultMappedMo
}
}
return
mappedModel
return
mappedModel
}
}
func
resolveOpenAIUpstreamModel
(
model
string
)
string
{
if
isBareGPT53CodexSparkModel
(
model
)
{
return
"gpt-5.3-codex-spark"
}
return
normalizeCodexModel
(
strings
.
TrimSpace
(
model
))
}
func
isBareGPT53CodexSparkModel
(
model
string
)
bool
{
modelID
:=
strings
.
TrimSpace
(
model
)
if
modelID
==
""
{
return
false
}
if
strings
.
Contains
(
modelID
,
"/"
)
{
parts
:=
strings
.
Split
(
modelID
,
"/"
)
modelID
=
parts
[
len
(
parts
)
-
1
]
}
normalized
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
modelID
))
return
normalized
==
"gpt-5.3-codex-spark"
||
normalized
==
"gpt 5.3 codex spark"
}
backend/internal/service/openai_model_mapping_test.go
View file @
995ef134
...
@@ -74,13 +74,30 @@ func TestResolveOpenAIForwardModel_PreventsClaudeModelFromFallingBackToGpt51(t *
...
@@ -74,13 +74,30 @@ func TestResolveOpenAIForwardModel_PreventsClaudeModelFromFallingBackToGpt51(t *
Credentials
:
map
[
string
]
any
{},
Credentials
:
map
[
string
]
any
{},
}
}
withoutDefault
:=
resolveOpenAIForwardModel
(
account
,
"claude-opus-4-6"
,
""
)
withoutDefault
:=
resolveOpenAIUpstreamModel
(
resolveOpenAIForwardModel
(
account
,
"claude-opus-4-6"
,
""
)
)
if
got
:=
normalizeCodexModel
(
withoutDefault
);
got
!=
"gpt-5.1"
{
if
withoutDefault
!=
"gpt-5.1"
{
t
.
Fatalf
(
"
normalizeCodex
Model(
%q
) = %q, want %q"
,
withoutDefault
,
got
,
"gpt-5.1"
)
t
.
Fatalf
(
"
resolveOpenAIUpstream
Model(
...
) = %q, want %q"
,
withoutDefault
,
"gpt-5.1"
)
}
}
withDefault
:=
resolveOpenAIForwardModel
(
account
,
"claude-opus-4-6"
,
"gpt-5.4"
)
withDefault
:=
resolveOpenAIUpstreamModel
(
resolveOpenAIForwardModel
(
account
,
"claude-opus-4-6"
,
"gpt-5.4"
))
if
got
:=
normalizeCodexModel
(
withDefault
);
got
!=
"gpt-5.4"
{
if
withDefault
!=
"gpt-5.4"
{
t
.
Fatalf
(
"normalizeCodexModel(%q) = %q, want %q"
,
withDefault
,
got
,
"gpt-5.4"
)
t
.
Fatalf
(
"resolveOpenAIUpstreamModel(...) = %q, want %q"
,
withDefault
,
"gpt-5.4"
)
}
}
func
TestResolveOpenAIUpstreamModel
(
t
*
testing
.
T
)
{
cases
:=
map
[
string
]
string
{
"gpt-5.3-codex-spark"
:
"gpt-5.3-codex-spark"
,
"gpt 5.3 codex spark"
:
"gpt-5.3-codex-spark"
,
" openai/gpt-5.3-codex-spark "
:
"gpt-5.3-codex-spark"
,
"gpt-5.3-codex-spark-high"
:
"gpt-5.3-codex"
,
"gpt-5.3-codex-spark-xhigh"
:
"gpt-5.3-codex"
,
"gpt-5.3"
:
"gpt-5.3-codex"
,
}
for
input
,
expected
:=
range
cases
{
if
got
:=
resolveOpenAIUpstreamModel
(
input
);
got
!=
expected
{
t
.
Fatalf
(
"resolveOpenAIUpstreamModel(%q) = %q, want %q"
,
input
,
got
,
expected
)
}
}
}
}
}
backend/internal/service/openai_ws_forwarder.go
View file @
995ef134
...
@@ -2515,12 +2515,9 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
...
@@ -2515,12 +2515,9 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
}
}
normalized = next
normalized = next
}
}
mappedModel
:=
account
.
GetMappedModel
(
originalModel
)
upstreamModel := resolveOpenAIUpstreamModel(account.GetMappedModel(originalModel))
if
normalizedModel
:=
normalizeCodexModel
(
mappedModel
);
normalizedModel
!=
""
{
if upstreamModel != originalModel {
mappedModel
=
normalizedModel
next, setErr := applyPayloadMutation(normalized, "model", upstreamModel)
}
if
mappedModel
!=
originalModel
{
next
,
setErr
:=
applyPayloadMutation
(
normalized
,
"model"
,
mappedModel
)
if setErr != nil {
if setErr != nil {
return openAIWSClientPayload{}, NewOpenAIWSClientCloseError(coderws.StatusPolicyViolation, "invalid websocket request payload", setErr)
return openAIWSClientPayload{}, NewOpenAIWSClientCloseError(coderws.StatusPolicyViolation, "invalid websocket request payload", setErr)
}
}
...
@@ -2776,10 +2773,7 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
...
@@ -2776,10 +2773,7 @@ func (s *OpenAIGatewayService) ProxyResponsesWebSocketFromClient(
mappedModel := ""
mappedModel := ""
var mappedModelBytes []byte
var mappedModelBytes []byte
if originalModel != "" {
if originalModel != "" {
mappedModel
=
account
.
GetMappedModel
(
originalModel
)
mappedModel = resolveOpenAIUpstreamModel(account.GetMappedModel(originalModel))
if
normalizedModel
:=
normalizeCodexModel
(
mappedModel
);
normalizedModel
!=
""
{
mappedModel
=
normalizedModel
}
needModelReplace = mappedModel != "" && mappedModel != originalModel
needModelReplace = mappedModel != "" && mappedModel != originalModel
if needModelReplace {
if needModelReplace {
mappedModelBytes = []byte(mappedModel)
mappedModelBytes = []byte(mappedModel)
...
...
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