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
773f20ed
Commit
773f20ed
authored
Feb 21, 2026
by
yangjianbo
Browse files
Merge branch 'test' into release
parents
987589ea
f323174d
Changes
5
Hide whitespace changes
Inline
Side-by-side
backend/internal/pkg/openai/request.go
View file @
773f20ed
...
...
@@ -15,41 +15,61 @@ var CodexOfficialClientUserAgentPrefixes = []string{
"codex_cli_rs/"
,
"codex_vscode/"
,
"codex_app/"
,
"codex_chatgpt_desktop/"
,
"codex_atlas/"
,
"codex_exec/"
,
"codex_sdk_ts/"
,
"codex "
,
}
// CodexOfficialClientOriginatorPrefixes matches Codex 官方客户端家族 originator 前缀。
// 说明:OpenAI 官方 Codex 客户端并不只使用固定的 codex_app 标识。
// 例如 codex_cli_rs、codex_vscode、codex_chatgpt_desktop、codex_atlas、codex_exec、codex_sdk_ts 等。
var
CodexOfficialClientOriginatorPrefixes
=
[]
string
{
"codex_"
,
"codex "
,
}
// IsCodexCLIRequest checks if the User-Agent indicates a Codex CLI request
func
IsCodexCLIRequest
(
userAgent
string
)
bool
{
ua
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
userAgent
)
)
ua
:=
normalizeCodexClientHeader
(
userAgent
)
if
ua
==
""
{
return
false
}
for
_
,
prefix
:=
range
CodexCLIUserAgentPrefixes
{
normalizedPrefix
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
prefix
))
if
normalizedPrefix
==
""
{
continue
}
// 优先前缀匹配;若 UA 被网关/代理拼接为复合字符串时,退化为包含匹配。
if
strings
.
HasPrefix
(
ua
,
normalizedPrefix
)
||
strings
.
Contains
(
ua
,
normalizedPrefix
)
{
return
true
}
}
return
false
return
matchCodexClientHeaderPrefixes
(
ua
,
CodexCLIUserAgentPrefixes
)
}
// IsCodexOfficialClientRequest checks if the User-Agent indicates a Codex 官方客户端请求。
// 与 IsCodexCLIRequest 解耦,避免影响历史兼容逻辑。
func
IsCodexOfficialClientRequest
(
userAgent
string
)
bool
{
ua
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
userAgent
)
)
ua
:=
normalizeCodexClientHeader
(
userAgent
)
if
ua
==
""
{
return
false
}
for
_
,
prefix
:=
range
CodexOfficialClientUserAgentPrefixes
{
normalizedPrefix
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
prefix
))
return
matchCodexClientHeaderPrefixes
(
ua
,
CodexOfficialClientUserAgentPrefixes
)
}
// IsCodexOfficialClientOriginator checks if originator indicates a Codex 官方客户端请求。
func
IsCodexOfficialClientOriginator
(
originator
string
)
bool
{
v
:=
normalizeCodexClientHeader
(
originator
)
if
v
==
""
{
return
false
}
return
matchCodexClientHeaderPrefixes
(
v
,
CodexOfficialClientOriginatorPrefixes
)
}
func
normalizeCodexClientHeader
(
value
string
)
string
{
return
strings
.
ToLower
(
strings
.
TrimSpace
(
value
))
}
func
matchCodexClientHeaderPrefixes
(
value
string
,
prefixes
[]
string
)
bool
{
for
_
,
prefix
:=
range
prefixes
{
normalizedPrefix
:=
normalizeCodexClientHeader
(
prefix
)
if
normalizedPrefix
==
""
{
continue
}
// 优先前缀匹配;若 UA 被网关
/代理
拼接为复合字符串时,退化为包含匹配。
if
strings
.
HasPrefix
(
ua
,
normalizedPrefix
)
||
strings
.
Contains
(
ua
,
normalizedPrefix
)
{
// 优先前缀匹配;若 UA
/Originator
被网关拼接为复合字符串时,退化为包含匹配。
if
strings
.
HasPrefix
(
value
,
normalizedPrefix
)
||
strings
.
Contains
(
value
,
normalizedPrefix
)
{
return
true
}
}
...
...
backend/internal/pkg/openai/request_test.go
View file @
773f20ed
...
...
@@ -36,6 +36,11 @@ func TestIsCodexOfficialClientRequest(t *testing.T) {
{
name
:
"codex_cli_rs 前缀"
,
ua
:
"codex_cli_rs/0.98.0"
,
want
:
true
},
{
name
:
"codex_vscode 前缀"
,
ua
:
"codex_vscode/1.0.0"
,
want
:
true
},
{
name
:
"codex_app 前缀"
,
ua
:
"codex_app/0.1.0"
,
want
:
true
},
{
name
:
"codex_chatgpt_desktop 前缀"
,
ua
:
"codex_chatgpt_desktop/1.0.0"
,
want
:
true
},
{
name
:
"codex_atlas 前缀"
,
ua
:
"codex_atlas/1.0.0"
,
want
:
true
},
{
name
:
"codex_exec 前缀"
,
ua
:
"codex_exec/0.1.0"
,
want
:
true
},
{
name
:
"codex_sdk_ts 前缀"
,
ua
:
"codex_sdk_ts/0.1.0"
,
want
:
true
},
{
name
:
"Codex 桌面 UA"
,
ua
:
"Codex Desktop/1.2.3"
,
want
:
true
},
{
name
:
"复合 UA 包含 codex_app"
,
ua
:
"Mozilla/5.0 codex_app/0.1.0"
,
want
:
true
},
{
name
:
"大小写混合"
,
ua
:
"Codex_VSCode/1.2.3"
,
want
:
true
},
{
name
:
"非 codex"
,
ua
:
"curl/8.0.1"
,
want
:
false
},
...
...
@@ -51,3 +56,32 @@ func TestIsCodexOfficialClientRequest(t *testing.T) {
})
}
}
func
TestIsCodexOfficialClientOriginator
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
originator
string
want
bool
}{
{
name
:
"codex_cli_rs"
,
originator
:
"codex_cli_rs"
,
want
:
true
},
{
name
:
"codex_vscode"
,
originator
:
"codex_vscode"
,
want
:
true
},
{
name
:
"codex_app"
,
originator
:
"codex_app"
,
want
:
true
},
{
name
:
"codex_chatgpt_desktop"
,
originator
:
"codex_chatgpt_desktop"
,
want
:
true
},
{
name
:
"codex_atlas"
,
originator
:
"codex_atlas"
,
want
:
true
},
{
name
:
"codex_exec"
,
originator
:
"codex_exec"
,
want
:
true
},
{
name
:
"codex_sdk_ts"
,
originator
:
"codex_sdk_ts"
,
want
:
true
},
{
name
:
"Codex 前缀"
,
originator
:
"Codex Desktop"
,
want
:
true
},
{
name
:
"空白包裹"
,
originator
:
" codex_vscode "
,
want
:
true
},
{
name
:
"非 codex"
,
originator
:
"my_client"
,
want
:
false
},
{
name
:
"空字符串"
,
originator
:
""
,
want
:
false
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
got
:=
IsCodexOfficialClientOriginator
(
tt
.
originator
)
if
got
!=
tt
.
want
{
t
.
Fatalf
(
"IsCodexOfficialClientOriginator(%q) = %v, want %v"
,
tt
.
originator
,
got
,
tt
.
want
)
}
})
}
}
backend/internal/service/openai_client_restriction_detector.go
View file @
773f20ed
...
...
@@ -11,6 +11,8 @@ const (
CodexClientRestrictionReasonDisabled
=
"codex_cli_only_disabled"
// CodexClientRestrictionReasonMatchedUA 表示请求命中官方客户端 UA 白名单。
CodexClientRestrictionReasonMatchedUA
=
"official_client_user_agent_matched"
// CodexClientRestrictionReasonMatchedOriginator 表示请求命中官方客户端 originator 白名单。
CodexClientRestrictionReasonMatchedOriginator
=
"official_client_originator_matched"
// CodexClientRestrictionReasonNotMatchedUA 表示请求未命中官方客户端 UA 白名单。
CodexClientRestrictionReasonNotMatchedUA
=
"official_client_user_agent_not_matched"
// CodexClientRestrictionReasonForceCodexCLI 表示通过 ForceCodexCLI 配置兜底放行。
...
...
@@ -56,8 +58,10 @@ func (d *OpenAICodexClientRestrictionDetector) Detect(c *gin.Context, account *A
}
userAgent
:=
""
originator
:=
""
if
c
!=
nil
{
userAgent
=
c
.
GetHeader
(
"User-Agent"
)
originator
=
c
.
GetHeader
(
"originator"
)
}
if
openai
.
IsCodexOfficialClientRequest
(
userAgent
)
{
return
CodexClientRestrictionDetectionResult
{
...
...
@@ -66,6 +70,13 @@ func (d *OpenAICodexClientRestrictionDetector) Detect(c *gin.Context, account *A
Reason
:
CodexClientRestrictionReasonMatchedUA
,
}
}
if
openai
.
IsCodexOfficialClientOriginator
(
originator
)
{
return
CodexClientRestrictionDetectionResult
{
Enabled
:
true
,
Matched
:
true
,
Reason
:
CodexClientRestrictionReasonMatchedOriginator
,
}
}
return
CodexClientRestrictionDetectionResult
{
Enabled
:
true
,
...
...
backend/internal/service/openai_client_restriction_detector_test.go
View file @
773f20ed
...
...
@@ -10,13 +10,16 @@ import (
"github.com/stretchr/testify/require"
)
func
newCodexDetectorTestContext
(
ua
string
)
*
gin
.
Context
{
func
newCodexDetectorTestContext
(
ua
string
,
originator
string
)
*
gin
.
Context
{
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/responses"
,
nil
)
if
ua
!=
""
{
c
.
Request
.
Header
.
Set
(
"User-Agent"
,
ua
)
}
if
originator
!=
""
{
c
.
Request
.
Header
.
Set
(
"originator"
,
originator
)
}
return
c
}
...
...
@@ -27,7 +30,7 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
detector
:=
NewOpenAICodexClientRestrictionDetector
(
nil
)
account
:=
&
Account
{
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeOAuth
,
Extra
:
map
[
string
]
any
{}}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
,
""
),
account
)
require
.
False
(
t
,
result
.
Enabled
)
require
.
False
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonDisabled
,
result
.
Reason
)
...
...
@@ -41,7 +44,7 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_cli_rs/0.99.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_cli_rs/0.99.0"
,
""
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
True
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonMatchedUA
,
result
.
Reason
)
...
...
@@ -55,7 +58,7 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_vscode/1.0.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_vscode/1.0.0"
,
""
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
True
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonMatchedUA
,
result
.
Reason
)
...
...
@@ -69,12 +72,26 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_app/2.1.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"codex_app/2.1.0"
,
""
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
True
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonMatchedUA
,
result
.
Reason
)
})
t
.
Run
(
"开启后 originator 命中"
,
func
(
t
*
testing
.
T
)
{
detector
:=
NewOpenAICodexClientRestrictionDetector
(
nil
)
account
:=
&
Account
{
Platform
:
PlatformOpenAI
,
Type
:
AccountTypeOAuth
,
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
,
"codex_chatgpt_desktop"
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
True
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonMatchedOriginator
,
result
.
Reason
)
})
t
.
Run
(
"开启后非官方客户端拒绝"
,
func
(
t
*
testing
.
T
)
{
detector
:=
NewOpenAICodexClientRestrictionDetector
(
nil
)
account
:=
&
Account
{
...
...
@@ -83,7 +100,7 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
,
"my_client"
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
False
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonNotMatchedUA
,
result
.
Reason
)
...
...
@@ -99,7 +116,7 @@ func TestOpenAICodexClientRestrictionDetector_Detect(t *testing.T) {
Extra
:
map
[
string
]
any
{
"codex_cli_only"
:
true
},
}
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
),
account
)
result
:=
detector
.
Detect
(
newCodexDetectorTestContext
(
"curl/8.0"
,
"my_client"
),
account
)
require
.
True
(
t
,
result
.
Enabled
)
require
.
True
(
t
,
result
.
Matched
)
require
.
Equal
(
t
,
CodexClientRestrictionReasonForceCodexCLI
,
result
.
Reason
)
...
...
backend/internal/service/openai_oauth_passthrough_test.go
View file @
773f20ed
...
...
@@ -555,12 +555,14 @@ func TestOpenAIGatewayService_CodexCLIOnly_AllowOfficialClientFamilies(t *testin
gin
.
SetMode
(
gin
.
TestMode
)
tests
:=
[]
struct
{
name
string
ua
string
name
string
ua
string
originator
string
}{
{
name
:
"codex_cli_rs"
,
ua
:
"codex_cli_rs/0.99.0"
},
{
name
:
"codex_vscode"
,
ua
:
"codex_vscode/1.0.0"
},
{
name
:
"codex_app"
,
ua
:
"codex_app/2.1.0"
},
{
name
:
"codex_cli_rs"
,
ua
:
"codex_cli_rs/0.99.0"
,
originator
:
""
},
{
name
:
"codex_vscode"
,
ua
:
"codex_vscode/1.0.0"
,
originator
:
""
},
{
name
:
"codex_app"
,
ua
:
"codex_app/2.1.0"
,
originator
:
""
},
{
name
:
"originator_codex_chatgpt_desktop"
,
ua
:
"curl/8.0"
,
originator
:
"codex_chatgpt_desktop"
},
}
for
_
,
tt
:=
range
tests
{
...
...
@@ -569,6 +571,9 @@ func TestOpenAIGatewayService_CodexCLIOnly_AllowOfficialClientFamilies(t *testin
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/responses"
,
bytes
.
NewReader
(
nil
))
c
.
Request
.
Header
.
Set
(
"User-Agent"
,
tt
.
ua
)
if
tt
.
originator
!=
""
{
c
.
Request
.
Header
.
Set
(
"originator"
,
tt
.
originator
)
}
inputBody
:=
[]
byte
(
`{"model":"gpt-5.2","stream":false,"store":true,"input":[{"type":"text","text":"hi"}]}`
)
...
...
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