fix: sync codex field (#6018)

This commit is contained in:
Seefs
2026-07-11 11:04:15 +08:00
committed by GitHub
parent 00f1cbb6df
commit dad57a6bb8
9 changed files with 162 additions and 14 deletions
+7 -2
View File
@@ -874,6 +874,9 @@ type OpenAIResponsesRequest struct {
User json.RawMessage `json:"user,omitempty"`
MaxToolCalls *uint `json:"max_tool_calls,omitempty"`
Prompt json.RawMessage `json:"prompt,omitempty"`
// Codex Responses metadata/client_metadata:
// https://github.com/openai/codex/commit/14df0e8833aad0d6d78287954b61ffac67af936c
ClientMetadata json.RawMessage `json:"client_metadata,omitempty"`
// qwen
EnableThinking json.RawMessage `json:"enable_thinking,omitempty"`
// perplexity
@@ -958,8 +961,10 @@ func (r *OpenAIResponsesRequest) GetToolsMap() []map[string]any {
}
type Reasoning struct {
Effort string `json:"effort,omitempty"`
Summary string `json:"summary,omitempty"`
Effort string `json:"effort,omitempty"`
Summary string `json:"summary,omitempty"`
Mode json.RawMessage `json:"mode,omitempty"`
Context json.RawMessage `json:"context,omitempty"`
}
type Input struct {
@@ -14,6 +14,15 @@ type OpenAIResponsesCompactionRequest struct {
Input json.RawMessage `json:"input,omitempty"`
Instructions json.RawMessage `json:"instructions,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"`
// Codex compact request parity:
// https://github.com/openai/codex/commit/53d59722268dde82fb93c1f37964ce196c2a86d7
// https://github.com/openai/codex/commit/5d6f23a27bf9c90709af527a7108c1c2eadf5123
Tools json.RawMessage `json:"tools,omitempty"`
ParallelToolCalls json.RawMessage `json:"parallel_tool_calls,omitempty"`
Reasoning *Reasoning `json:"reasoning,omitempty"`
ServiceTier string `json:"service_tier,omitempty"`
PromptCacheKey json.RawMessage `json:"prompt_cache_key,omitempty"`
Text json.RawMessage `json:"text,omitempty"`
}
func (r *OpenAIResponsesCompactionRequest) GetTokenCountMeta() *types.TokenCountMeta {
+1 -1
View File
@@ -9,7 +9,7 @@ var baseModelList = []string{
"gpt-5", "gpt-5-codex", "gpt-5-codex-mini",
"gpt-5.1", "gpt-5.1-codex", "gpt-5.1-codex-max", "gpt-5.1-codex-mini",
"gpt-5.2", "gpt-5.2-codex", "gpt-5.3-codex", "gpt-5.3-codex-spark",
"gpt-5.4",
"gpt-5.4", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
}
var ModelList = withCompactModelSuffix(baseModelList)
+20
View File
@@ -14,6 +14,7 @@ import (
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/logger"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/bytedance/gopkg/util/gopool"
@@ -45,6 +46,24 @@ func NewStreamScanner(reader io.Reader) *bufio.Scanner {
return scanner
}
func copyCodexSSEHeaders(c *gin.Context, resp *http.Response) {
if c == nil || c.Writer == nil || resp == nil {
return
}
// codex
for _, name := range []string{"X-Reasoning-Included", "X-Codex-Turn-State"} {
values := resp.Header.Values(name)
if !service.ShouldCopyUpstreamHeader(c, name, values) {
continue
}
for _, value := range values {
if value != "" {
c.Writer.Header().Add(name, value)
}
}
}
}
// ExtendWriteDeadline pushes the connection write deadline forward before each
// stream write. Best-effort: writers that don't support deadlines (e.g.
// httptest recorders) are silently ignored.
@@ -122,6 +141,7 @@ func StreamScannerHandler(c *gin.Context, resp *http.Response, info *relaycommon
defer cleanup()
scanner.Split(bufio.ScanLines)
copyCodexSSEHeaders(c, resp)
SetEventStreamHeaders(c)
ctx = context.WithValue(ctx, "stop_chan", stopChan)
+2
View File
@@ -45,6 +45,8 @@ func ResponsesHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *
Input: req.Input,
Instructions: req.Instructions,
PreviousResponseID: req.PreviousResponseID,
ParallelToolCalls: req.ParallelToolCalls,
ServiceTier: req.ServiceTier,
}
default:
return types.NewErrorWithStatusCode(
@@ -36,12 +36,33 @@ type ChannelAffinitySetting struct {
Rules []ChannelAffinityRule `json:"rules"`
}
// Keep Codex CLI passthrough aligned with upstream. Codex uses lower-case
// header names, while HTTP matching here is case-insensitive.
// Request session/thread headers:
// https://github.com/openai/codex/commit/7c7b4861d88960f7e3bd5b7f30f8351be666dd84
// Responses metadata headers/client_metadata:
// https://github.com/openai/codex/commit/14df0e8833aad0d6d78287954b61ffac67af936c
// x-codex-turn-state response/request round trip:
// https://github.com/openai/codex/commit/ebdd8795e924a8149b616e46ca2ed7848c207a4b
var codexCliPassThroughHeaders = []string{
"Originator",
"Session_id",
"Thread_id",
"Session-Id",
"Thread-Id",
"X-Client-Request-Id",
"User-Agent",
"X-Codex-Beta-Features",
"X-Codex-Turn-State",
"X-Codex-Turn-Metadata",
"X-Codex-Window-Id",
"X-Codex-Parent-Thread-Id",
//"X-Codex-Installation-Id",
"X-OpenAI-Subagent",
"X-OpenAI-Memgen-Request",
//"X-OAI-Attestation",
"X-ResponsesAPI-Include-Timing-Metrics",
"X-OpenAI-Internal-Codex-Responses-Lite",
}
var claudeCliPassThroughHeaders = []string{
@@ -74,6 +95,20 @@ func buildPassHeaderTemplate(headers []string) map[string]interface{} {
}
}
func buildCodexPassHeaderTemplate() map[string]interface{} {
requestHeaders := make([]string, 0, len(codexCliPassThroughHeaders))
requestHeaders = append(requestHeaders, codexCliPassThroughHeaders...)
return map[string]interface{}{
"operations": []map[string]interface{}{
{
"mode": "pass_headers",
"value": requestHeaders,
"keep_origin": true,
},
},
}
}
var channelAffinitySetting = ChannelAffinitySetting{
Enabled: true,
SwitchOnSuccess: true,
@@ -90,7 +125,7 @@ var channelAffinitySetting = ChannelAffinitySetting{
},
ValueRegex: "",
TTLSeconds: 0,
ParamOverrideTemplate: buildPassHeaderTemplate(codexCliPassThroughHeaders),
ParamOverrideTemplate: buildCodexPassHeaderTemplate(),
SkipRetryOnFailure: true,
IncludeUsingGroup: true,
IncludeRuleName: true,
@@ -27,12 +27,39 @@ const buildPassHeadersTemplate = (headers) => ({
],
});
const buildCodexPassHeadersTemplate = () => ({
operations: [
{
mode: 'pass_headers',
value: [...CODEX_CLI_HEADER_PASSTHROUGH_HEADERS],
keep_origin: true,
},
],
});
// Keep in sync with upstream Codex request headers:
// https://github.com/openai/codex/commit/7c7b4861d88960f7e3bd5b7f30f8351be666dd84
// https://github.com/openai/codex/commit/14df0e8833aad0d6d78287954b61ffac67af936c
// https://github.com/openai/codex/commit/ebdd8795e924a8149b616e46ca2ed7848c207a4b
export const CODEX_CLI_HEADER_PASSTHROUGH_HEADERS = [
'Originator',
'Session_id',
'Thread_id',
'Session-Id',
'Thread-Id',
'X-Client-Request-Id',
'User-Agent',
'X-Codex-Beta-Features',
'X-Codex-Turn-State',
'X-Codex-Turn-Metadata',
'X-Codex-Window-Id',
'X-Codex-Parent-Thread-Id',
// 'X-Codex-Installation-Id',
'X-OpenAI-Subagent',
'X-OpenAI-Memgen-Request',
// 'X-OAI-Attestation',
'X-ResponsesAPI-Include-Timing-Metrics',
'X-OpenAI-Internal-Codex-Responses-Lite',
];
export const CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS = [
@@ -51,9 +78,8 @@ export const CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS = [
'Anthropic-Version',
];
export const CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE = buildPassHeadersTemplate(
CODEX_CLI_HEADER_PASSTHROUGH_HEADERS,
);
export const CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE =
buildCodexPassHeadersTemplate();
export const CLAUDE_CLI_HEADER_PASSTHROUGH_TEMPLATE = buildPassHeadersTemplate(
CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS,
@@ -65,7 +91,7 @@ export const CHANNEL_AFFINITY_RULE_TEMPLATES = {
model_regex: ['^gpt-.*$'],
path_regex: ['/v1/responses'],
key_sources: [{ type: 'gjson', path: 'prompt_cache_key' }],
param_override_template: CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE,
param_override_template: buildCodexPassHeadersTemplate(),
value_regex: '',
ttl_seconds: 0,
skip_retry_on_failure: true,
@@ -291,12 +291,29 @@ const GEMINI_IMAGE_4K_TEMPLATE = {
],
}
// Keep in sync with upstream Codex request headers:
// https://github.com/openai/codex/commit/7c7b4861d88960f7e3bd5b7f30f8351be666dd84
// https://github.com/openai/codex/commit/14df0e8833aad0d6d78287954b61ffac67af936c
// https://github.com/openai/codex/commit/ebdd8795e924a8149b616e46ca2ed7848c207a4b
const CODEX_CLI_HEADER_PASSTHROUGH_HEADERS = [
'Originator',
'Session_id',
'Thread_id',
'Session-Id',
'Thread-Id',
'X-Client-Request-Id',
'User-Agent',
'X-Codex-Beta-Features',
'X-Codex-Turn-State',
'X-Codex-Turn-Metadata',
'X-Codex-Window-Id',
'X-Codex-Parent-Thread-Id',
// 'X-Codex-Installation-Id',
'X-OpenAI-Subagent',
'X-OpenAI-Memgen-Request',
// 'X-OAI-Attestation',
'X-ResponsesAPI-Include-Timing-Metrics',
'X-OpenAI-Internal-Codex-Responses-Lite',
]
const CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS = [
@@ -321,9 +338,15 @@ const buildPassHeadersTemplate = (headers: string[]) => ({
],
})
const CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE = buildPassHeadersTemplate(
CODEX_CLI_HEADER_PASSTHROUGH_HEADERS
)
const CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE = {
operations: [
{
mode: 'pass_headers',
value: [...CODEX_CLI_HEADER_PASSTHROUGH_HEADERS],
keep_origin: true,
},
],
}
const CLAUDE_CLI_HEADER_PASSTHROUGH_TEMPLATE = buildPassHeadersTemplate(
CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS
)
@@ -941,6 +964,7 @@ const validateOperations = (
if (headers.length === 0)
return t('Rule {{line}} pass_headers format is invalid', { line })
}
}
return ''
}
@@ -18,12 +18,29 @@ For commercial licensing, please contact support@quantumnous.com
*/
import type { AffinityRule } from './types'
// Keep in sync with upstream Codex request headers:
// https://github.com/openai/codex/commit/7c7b4861d88960f7e3bd5b7f30f8351be666dd84
// https://github.com/openai/codex/commit/14df0e8833aad0d6d78287954b61ffac67af936c
// https://github.com/openai/codex/commit/ebdd8795e924a8149b616e46ca2ed7848c207a4b
const CODEX_CLI_HEADER_PASSTHROUGH_HEADERS = [
'Originator',
'Session_id',
'Thread_id',
'Session-Id',
'Thread-Id',
'X-Client-Request-Id',
'User-Agent',
'X-Codex-Beta-Features',
'X-Codex-Turn-State',
'X-Codex-Turn-Metadata',
'X-Codex-Window-Id',
'X-Codex-Parent-Thread-Id',
// 'X-Codex-Installation-Id',
'X-OpenAI-Subagent',
'X-OpenAI-Memgen-Request',
// 'X-OAI-Attestation',
'X-ResponsesAPI-Include-Timing-Metrics',
'X-OpenAI-Internal-Codex-Responses-Lite',
]
const CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS = [
@@ -54,6 +71,18 @@ function buildPassHeadersTemplate(headers: string[]) {
}
}
function buildCodexPassHeadersTemplate() {
return {
operations: [
{
mode: 'pass_headers',
value: [...CODEX_CLI_HEADER_PASSTHROUGH_HEADERS],
keep_origin: true,
},
],
}
}
export type RuleTemplate = Omit<AffinityRule, 'id'>
export const RULE_TEMPLATES: Record<string, RuleTemplate> = {
@@ -62,9 +91,7 @@ export const RULE_TEMPLATES: Record<string, RuleTemplate> = {
model_regex: ['^gpt-.*$'],
path_regex: ['/v1/responses'],
key_sources: [{ type: 'gjson', path: 'prompt_cache_key' }],
param_override_template: buildPassHeadersTemplate(
CODEX_CLI_HEADER_PASSTHROUGH_HEADERS
),
param_override_template: buildCodexPassHeadersTemplate(),
value_regex: '',
ttl_seconds: 0,
skip_retry_on_failure: true,