Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions dto/openai_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions dto/openai_responses_compaction_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion relay/channel/codex/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions relay/helper/stream_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions relay/responses_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
37 changes: 36 additions & 1 deletion setting/operation_setting/channel_affinity_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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,
Expand All @@ -90,7 +125,7 @@ var channelAffinitySetting = ChannelAffinitySetting{
},
ValueRegex: "",
TTLSeconds: 0,
ParamOverrideTemplate: buildPassHeaderTemplate(codexCliPassThroughHeaders),
ParamOverrideTemplate: buildCodexPassHeaderTemplate(),
SkipRetryOnFailure: true,
IncludeUsingGroup: true,
IncludeRuleName: true,
Expand Down
34 changes: 30 additions & 4 deletions web/classic/src/constants/channel-affinity-template.constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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
)
Expand Down Expand Up @@ -947,6 +970,7 @@ const validateOperations = (
if (headers.length === 0)
return t('Rule {{line}} pass_headers format is invalid', { line })
}

}
return ''
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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> = {
Expand All @@ -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,
Expand Down