mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 16:08:02 +08:00
fix: unify Codex OAuth outbound identity onto the inference resolver
Token exchange, PAT whoami, models, probes, and pre-writes now follow the same UA/version chain as Codex inference instead of hardcoded codex-cli/0.91.0 or compile-time constants.
This commit is contained in:
@@ -48,7 +48,7 @@ func (s *openaiOAuthService) ExchangeCode(ctx context.Context, code, codeVerifie
|
||||
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetHeader("User-Agent", "codex-cli/0.91.0").
|
||||
SetHeader("User-Agent", service.CodexCanonicalUserAgent()).
|
||||
SetFormDataFromValues(formData).
|
||||
SetSuccessResult(&tokenResp).
|
||||
Post(s.tokenURL)
|
||||
@@ -96,7 +96,7 @@ func (s *openaiOAuthService) refreshTokenWithClientID(ctx context.Context, refre
|
||||
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetHeader("User-Agent", "codex-cli/0.91.0").
|
||||
SetHeader("User-Agent", service.CodexCanonicalUserAgent()).
|
||||
SetFormDataFromValues(formData).
|
||||
SetSuccessResult(&tokenResp).
|
||||
Post(s.tokenURL)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@@ -77,6 +78,11 @@ func (s *OpenAIOAuthServiceSuite) TestExchangeCode_DefaultRedirectURI() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if got := r.Header.Get("User-Agent"); got != service.CodexCanonicalUserAgent() {
|
||||
errCh <- "user-agent mismatch"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = io.WriteString(w, `{"access_token":"at","refresh_token":"rt","token_type":"bearer","expires_in":3600}`)
|
||||
@@ -121,6 +127,11 @@ func (s *OpenAIOAuthServiceSuite) TestRefreshToken_FormFields() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if got := r.Header.Get("User-Agent"); got != service.CodexCanonicalUserAgent() {
|
||||
errCh <- "user-agent mismatch"
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = io.WriteString(w, `{"access_token":"at2","refresh_token":"rt2","token_type":"bearer","expires_in":3600}`)
|
||||
|
||||
@@ -731,11 +731,12 @@ func (s *AccountTestService) testOpenAIAccountConnection(c *gin.Context, account
|
||||
req.Host = "chatgpt.com"
|
||||
req.Header.Set("accept", "text/event-stream")
|
||||
req.Header.Set("OpenAI-Beta", "responses=experimental")
|
||||
req.Header.Set("Originator", openai.CodexDefaultOriginator)
|
||||
canonical := resolveCodexOutboundIdentity("")
|
||||
req.Header.Set("Originator", canonical.originator)
|
||||
if customUA := strings.TrimSpace(credentialAccount.GetOpenAIUserAgent()); customUA != "" {
|
||||
req.Header.Set("User-Agent", customUA)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
setOpenAIChatGPTAccountHeaders(req.Header, credentialAccount)
|
||||
// 与真实转发一致:账号级自定义 UA 同样作为管理员显式配置传入,否则测试用的身份
|
||||
@@ -2063,6 +2064,9 @@ func (s *AccountTestService) testOpenAICompactConnection(c *gin.Context, account
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
}
|
||||
applyOpenAICodexProbeHeaders(req.Header)
|
||||
if isOAuth {
|
||||
enforceCodexIdentityHeadersWithUA(req.Header, credentialAccount.GetOpenAIUserAgent())
|
||||
}
|
||||
probeSessionID := compactProbeSessionID(account.ID)
|
||||
req.Header.Set("Session_ID", probeSessionID)
|
||||
req.Header.Set("Conversation_ID", probeSessionID)
|
||||
@@ -2968,11 +2972,12 @@ func (s *AccountTestService) testOpenAIImageOAuth(c *gin.Context, ctx context.Co
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
req.Header.Set("OpenAI-Beta", "responses=experimental")
|
||||
req.Header.Set("originator", openai.CodexDefaultOriginator)
|
||||
canonical := resolveCodexOutboundIdentity("")
|
||||
req.Header.Set("originator", canonical.originator)
|
||||
if customUA := strings.TrimSpace(credentialAccount.GetOpenAIUserAgent()); customUA != "" {
|
||||
req.Header.Set("User-Agent", customUA)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
setOpenAIChatGPTAccountHeaders(req.Header, credentialAccount)
|
||||
// 与真实转发一致:账号级自定义 UA 同样作为管理员显式配置传入,否则测试用的身份
|
||||
|
||||
@@ -113,7 +113,7 @@ const (
|
||||
openAIProbeCacheTTL = 10 * time.Minute
|
||||
grokProbeRetryTTL = 1 * time.Minute
|
||||
grokFreeQuotaWindow = 24 * time.Hour
|
||||
openAICodexProbeVersion = codexCLIVersion // 与网关出站身份同源,避免两处硬编码版本各自漂移
|
||||
openAICodexProbeVersion = codexCLIVersion // 编译期兜底;运行时探针 version 走 CodexCanonicalClientVersion
|
||||
)
|
||||
|
||||
// UsageCache 封装账户使用量相关的缓存
|
||||
@@ -860,9 +860,10 @@ func (s *AccountUsageService) probeOpenAICodexSnapshot(ctx context.Context, acco
|
||||
}
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
req.Header.Set("OpenAI-Beta", "responses=experimental")
|
||||
req.Header.Set("Originator", openaipkg.CodexDefaultOriginator)
|
||||
req.Header.Set("Version", openAICodexProbeVersion)
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
canonical := resolveCodexOutboundIdentity("")
|
||||
req.Header.Set("Originator", canonical.originator)
|
||||
req.Header.Set("Version", canonical.version)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
if s.identityCache != nil {
|
||||
if fp, fpErr := s.identityCache.GetFingerprint(reqCtx, account.ID); fpErr == nil && fp != nil && strings.TrimSpace(fp.UserAgent) != "" {
|
||||
req.Header.Set("User-Agent", strings.TrimSpace(fp.UserAgent))
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
@@ -239,25 +238,26 @@ func (s *OpenAIGatewayService) buildOpenAIAlphaSearchResponsesWebSearchRequest(c
|
||||
if turnMetadata := openAIAlphaSearchInboundHeader(c, "X-Codex-Turn-Metadata"); turnMetadata != "" {
|
||||
req.Header.Set("X-Codex-Turn-Metadata", turnMetadata)
|
||||
}
|
||||
canonical := resolveCodexOutboundIdentity("")
|
||||
if version := openAIAlphaSearchInboundHeader(c, "Version"); version != "" {
|
||||
req.Header.Set("Version", version)
|
||||
} else {
|
||||
req.Header.Set("Version", codexCLIVersion)
|
||||
req.Header.Set("Version", canonical.version)
|
||||
}
|
||||
if originator := openAIAlphaSearchInboundHeader(c, "Originator"); originator != "" {
|
||||
req.Header.Set("Originator", originator)
|
||||
} else {
|
||||
req.Header.Set("Originator", openai.CodexDefaultOriginator)
|
||||
req.Header.Set("Originator", canonical.originator)
|
||||
}
|
||||
if customUA := account.GetOpenAIUserAgent(); customUA != "" {
|
||||
req.Header.Set("User-Agent", customUA)
|
||||
} else if userAgent := openAIAlphaSearchInboundHeader(c, "User-Agent"); userAgent != "" {
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
if s.cfg != nil && s.cfg.Gateway.ForceCodexCLI {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
apiKeyID := getAPIKeyIDFromContext(c)
|
||||
if sessionID := strings.TrimSpace(gjson.GetBytes(alphaBody, "id").String()); sessionID != "" {
|
||||
@@ -382,25 +382,26 @@ func (s *OpenAIGatewayService) buildOpenAIAlphaSearchRequest(ctx context.Context
|
||||
if turnMetadata := openAIAlphaSearchInboundHeader(c, "X-Codex-Turn-Metadata"); turnMetadata != "" {
|
||||
req.Header.Set("X-Codex-Turn-Metadata", turnMetadata)
|
||||
}
|
||||
canonical := resolveCodexOutboundIdentity("")
|
||||
if version := openAIAlphaSearchInboundHeader(c, "Version"); version != "" {
|
||||
req.Header.Set("Version", version)
|
||||
} else {
|
||||
req.Header.Set("Version", codexCLIVersion)
|
||||
req.Header.Set("Version", canonical.version)
|
||||
}
|
||||
if originator := openAIAlphaSearchInboundHeader(c, "Originator"); originator != "" {
|
||||
req.Header.Set("Originator", originator)
|
||||
} else {
|
||||
req.Header.Set("Originator", openai.CodexDefaultOriginator)
|
||||
req.Header.Set("Originator", canonical.originator)
|
||||
}
|
||||
if customUA := account.GetOpenAIUserAgent(); customUA != "" {
|
||||
req.Header.Set("User-Agent", customUA)
|
||||
} else if userAgent := openAIAlphaSearchInboundHeader(c, "User-Agent"); userAgent != "" {
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
if s.cfg != nil && s.cfg.Gateway.ForceCodexCLI {
|
||||
req.Header.Set("User-Agent", codexCLIUserAgent)
|
||||
req.Header.Set("User-Agent", canonical.userAgent)
|
||||
}
|
||||
enforceCodexIdentityHeadersWithUA(req.Header, s.codexIdentityOverrideUA(account))
|
||||
}
|
||||
|
||||
@@ -76,6 +76,29 @@ func SetCodexCanonicalUserAgentResolver(resolver func() string) {
|
||||
codexCanonicalUAResolver = resolver
|
||||
}
|
||||
|
||||
// CodexCanonicalUserAgent 返回当前生效的规范 Codex User-Agent。
|
||||
// 取值走与推理相同的解析链:面板 UA 指纹 + 面板/自动同步版本号 + 编译期兜底。
|
||||
// 供无账号句柄的出站路径(OAuth 换 Token / 刷新)使用。
|
||||
func CodexCanonicalUserAgent() string {
|
||||
return resolveCodexOutboundIdentity("").userAgent
|
||||
}
|
||||
|
||||
// ApplyCodexCanonicalIdentity 为无账号句柄的出站请求写入与推理同源的身份三元组。
|
||||
func ApplyCodexCanonicalIdentity(h http.Header) {
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
identity := resolveCodexOutboundIdentity("")
|
||||
h.Set("user-agent", identity.userAgent)
|
||||
h.Set("originator", identity.originator)
|
||||
h.Set("version", identity.version)
|
||||
}
|
||||
|
||||
// CodexCanonicalClientVersion 返回当前生效的 Codex 客户端版本号。
|
||||
func CodexCanonicalClientVersion() string {
|
||||
return resolveCodexOutboundIdentity("").version
|
||||
}
|
||||
|
||||
// codexCanonicalUserAgent 返回出站规范 User-Agent。
|
||||
func codexCanonicalUserAgent() string {
|
||||
codexCanonicalUAMu.RLock()
|
||||
@@ -210,6 +233,6 @@ func pairCodexIdentityHeaders(h http.Header) {
|
||||
h.Set("user-agent", pairedUA)
|
||||
h.Set("originator", originator)
|
||||
if v := strings.TrimSpace(h.Get("version")); v != "" && CompareVersions(v, codexUpstreamMinVersion) < 0 {
|
||||
h.Set("version", codexCLIVersion)
|
||||
h.Set("version", resolveCodexOutboundIdentity("").version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,3 +322,26 @@ func TestBuildCodexCLIUserAgent(t *testing.T) {
|
||||
require.Equal(t, codexCLIUserAgent, buildCodexCLIUserAgent("bogus version"))
|
||||
require.Equal(t, codexCLIUserAgent, buildCodexCLIUserAgent(""))
|
||||
}
|
||||
|
||||
func TestCodexCanonicalUserAgentFollowsResolver(t *testing.T) {
|
||||
SetCodexCanonicalUserAgentResolver(func() string {
|
||||
return "codex_cli_rs/0.200.1" + codexCLIUserAgentSuffix
|
||||
})
|
||||
t.Cleanup(func() { SetCodexCanonicalUserAgentResolver(nil) })
|
||||
|
||||
require.Equal(t, "codex_cli_rs/0.200.1"+codexCLIUserAgentSuffix, CodexCanonicalUserAgent())
|
||||
require.Equal(t, "0.200.1", CodexCanonicalClientVersion())
|
||||
|
||||
h := make(http.Header)
|
||||
ApplyCodexCanonicalIdentity(h)
|
||||
require.Equal(t, "codex_cli_rs", h.Get("originator"))
|
||||
require.Equal(t, "codex_cli_rs/0.200.1"+codexCLIUserAgentSuffix, h.Get("user-agent"))
|
||||
require.Equal(t, "0.200.1", h.Get("version"))
|
||||
}
|
||||
|
||||
func TestCodexCanonicalUserAgentFallsBackWithoutResolver(t *testing.T) {
|
||||
SetCodexCanonicalUserAgentResolver(nil)
|
||||
|
||||
require.Equal(t, codexCLIUserAgent, CodexCanonicalUserAgent())
|
||||
require.Equal(t, codexCLIVersion, CodexCanonicalClientVersion())
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/httpclient"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
@@ -243,7 +242,7 @@ func (s *OpenAIGatewayService) FetchCodexModelsManifest(ctx context.Context, acc
|
||||
|
||||
clientVersion = strings.TrimSpace(clientVersion)
|
||||
if clientVersion == "" {
|
||||
clientVersion = openAICodexProbeVersion
|
||||
clientVersion = CodexCanonicalClientVersion()
|
||||
}
|
||||
|
||||
requestEndpoint := chatgptCodexModelsURL
|
||||
@@ -305,9 +304,14 @@ func (s *OpenAIGatewayService) FetchCodexModelsManifest(ctx context.Context, acc
|
||||
setOpenAIChatGPTAccountHeaders(headers, credAccount)
|
||||
}
|
||||
headers.Set("Accept", "application/json")
|
||||
headers.Set("Originator", openai.CodexDefaultOriginator)
|
||||
headers.Set("Version", clientVersion)
|
||||
headers.Set("User-Agent", codexCLIUserAgent)
|
||||
overrideUA := ""
|
||||
if !useAPIKeyUpstream {
|
||||
overrideUA = credAccount.GetOpenAIUserAgent()
|
||||
}
|
||||
identity := resolveCodexOutboundIdentity(overrideUA)
|
||||
headers.Set("Originator", identity.originator)
|
||||
headers.Set("User-Agent", identity.userAgent)
|
||||
headers.Set("Version", identity.version)
|
||||
|
||||
proxyURL := ""
|
||||
if account.ProxyID != nil && account.Proxy != nil {
|
||||
|
||||
@@ -348,8 +348,8 @@ func TestFetchCodexModelsManifestDefaultClientVersion(t *testing.T) {
|
||||
if _, err := s.FetchCodexModelsManifest(context.Background(), newCodexModelsTestAccount(), "", ""); err != nil {
|
||||
t.Fatalf("FetchCodexModelsManifest returned error: %v", err)
|
||||
}
|
||||
if gotClientVersion != openAICodexProbeVersion {
|
||||
t.Errorf("default client_version: got %q, want %q", gotClientVersion, openAICodexProbeVersion)
|
||||
if gotClientVersion != CodexCanonicalClientVersion() {
|
||||
t.Errorf("default client_version: got %q, want %q", gotClientVersion, CodexCanonicalClientVersion())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,10 +451,10 @@ func TestFetchCodexModelsManifestAPIKeyCustomUpstream(t *testing.T) {
|
||||
if gotRequest.Header.Get("Originator") != openai.CodexDefaultOriginator {
|
||||
t.Errorf("originator header: got %q", gotRequest.Header.Get("Originator"))
|
||||
}
|
||||
if gotRequest.Header.Get("Version") != "0.144.0" {
|
||||
if gotRequest.Header.Get("Version") != CodexCanonicalClientVersion() {
|
||||
t.Errorf("version header: got %q", gotRequest.Header.Get("Version"))
|
||||
}
|
||||
if gotRequest.Header.Get("User-Agent") != codexCLIUserAgent {
|
||||
if gotRequest.Header.Get("User-Agent") != CodexCanonicalUserAgent() {
|
||||
t.Errorf("user-agent header: got %q", gotRequest.Header.Get("User-Agent"))
|
||||
}
|
||||
if gotRequest.Header.Get("chatgpt-account-id") != "" {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/httpclient"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
)
|
||||
|
||||
const openAICodexPATWhoamiURLDefault = "https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami"
|
||||
@@ -59,8 +58,7 @@ func (s *OpenAIOAuthService) ValidateCodexPersonalAccessToken(ctx context.Contex
|
||||
}
|
||||
req.Header.Set("authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("accept", "application/json")
|
||||
req.Header.Set("originator", openai.CodexDefaultOriginator)
|
||||
req.Header.Set("user-agent", codexCLIUserAgent)
|
||||
ApplyCodexCanonicalIdentity(req.Header)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestOpenAIOAuthService_ValidateCodexPersonalAccessToken(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Bearer at-test-token", gotAuthorization)
|
||||
require.Equal(t, openai.CodexDefaultOriginator, gotOriginator)
|
||||
require.Equal(t, codexCLIUserAgent, gotUserAgent)
|
||||
require.Equal(t, CodexCanonicalUserAgent(), gotUserAgent)
|
||||
require.Equal(t, OpenAIAuthModePersonalAccessToken, info.AuthMode)
|
||||
require.Equal(t, "user@example.com", info.Email)
|
||||
require.Equal(t, "user-123", info.ChatGPTUserID)
|
||||
|
||||
@@ -1128,7 +1128,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
|
||||
if isOpenAIResponsesCompactPath(c) {
|
||||
req.Header.Set("accept", "application/json")
|
||||
if req.Header.Get("version") == "" {
|
||||
req.Header.Set("version", codexCLIVersion)
|
||||
req.Header.Set("version", CodexCanonicalClientVersion())
|
||||
}
|
||||
compactSession := resolveOpenAICompactSessionID(c)
|
||||
req.Header.Set("session_id", isolateOpenAISessionID(apiKeyID, compactSession))
|
||||
@@ -1154,10 +1154,10 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
|
||||
req.Header.Set("user-agent", customUA)
|
||||
}
|
||||
|
||||
// 若开启 ForceCodexCLI,则强制将上游 User-Agent 伪装为 Codex CLI。
|
||||
// 若开启 ForceCodexCLI,则强制将上游 User-Agent 伪装为规范 Codex 身份。
|
||||
// 用于网关未透传/改写 User-Agent 时,仍能命中 Codex 侧识别逻辑。
|
||||
if s.cfg != nil && s.cfg.Gateway.ForceCodexCLI {
|
||||
req.Header.Set("user-agent", codexCLIUserAgent)
|
||||
req.Header.Set("user-agent", CodexCanonicalUserAgent())
|
||||
}
|
||||
|
||||
// 指纹收敛:使用 Forward() 中预计算的收敛 ID 改写出站头,与请求体使用同一份 IDs。
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
"github.com/Wei-Shaw/sub2api/internal/util/responseheaders"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
@@ -445,7 +444,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
|
||||
if isOpenAIResponsesCompactPath(c) {
|
||||
req.Header.Set("accept", "application/json")
|
||||
if req.Header.Get("version") == "" {
|
||||
req.Header.Set("version", codexCLIVersion)
|
||||
req.Header.Set("version", CodexCanonicalClientVersion())
|
||||
}
|
||||
if clientSessionID == "" {
|
||||
clientSessionID = resolveOpenAICompactSessionID(c)
|
||||
@@ -454,7 +453,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
|
||||
req.Header.Set("accept", "text/event-stream")
|
||||
}
|
||||
if req.Header.Get("originator") == "" {
|
||||
req.Header.Set("originator", openai.CodexDefaultOriginator)
|
||||
req.Header.Set("originator", resolveCodexOutboundIdentity("").originator)
|
||||
}
|
||||
// 用隔离后的 session 标识符覆盖客户端透传值,防止跨用户会话碰撞。
|
||||
if clientSessionID == "" {
|
||||
@@ -482,7 +481,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
|
||||
req.Header.Set("user-agent", customUA)
|
||||
}
|
||||
if s.cfg != nil && s.cfg.Gateway.ForceCodexCLI {
|
||||
req.Header.Set("user-agent", codexCLIUserAgent)
|
||||
req.Header.Set("user-agent", CodexCanonicalUserAgent())
|
||||
}
|
||||
// 指纹收敛:使用 forwardOpenAIPassthrough 中预计算的收敛 ID 改写出站头,
|
||||
// 与请求体 client_metadata 共享同一份 IDs(与非透传路径相同的相对位置:
|
||||
|
||||
@@ -161,7 +161,7 @@ func (s *OpenAIGatewayService) buildOpenAIWSHeaders(
|
||||
}
|
||||
}
|
||||
if s != nil && s.cfg != nil && s.cfg.Gateway.ForceCodexCLI {
|
||||
headers.Set("user-agent", codexCLIUserAgent)
|
||||
headers.Set("user-agent", CodexCanonicalUserAgent())
|
||||
}
|
||||
// 终态收口:WS 握手与 HTTP 出站共用同一套身份语义,账号级自定义 UA 同样作为
|
||||
// 管理员显式配置传入(上面写进 headers 的值只在强制统一被关闭时才参与配对)。
|
||||
|
||||
Reference in New Issue
Block a user