mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 09:37:37 +08:00
[codex] Stabilize Codex OAuth cache routing (#2218)
* Stabilize Codex OAuth cache routing Codex OAuth-backed Claude proxy requests now reuse a client-provided session identity for prompt cache routing and send Codex-like session headers when that identity exists. Generated proxy UUIDs are intentionally excluded so they do not fragment cache locality.\n\nThe same path exposed two runtime issues during validation: rustls needed an explicit process crypto provider, and Codex OAuth can return Responses SSE even when the original Claude request is non-streaming. Those are handled so cache-routed requests can complete instead of panicking or being parsed as JSON.\n\nConstraint: Official Codex uses conversation identity and Responses session headers for prompt cache routing.\nRejected: Always use generated proxy session IDs | generated IDs change per request and reduce cache reuse.\nConfidence: medium\nScope-risk: moderate\nDirective: Do not remove the client-provided-session guard unless generated session IDs become stable per conversation.\nTested: cargo test codex_oauth\nTested: Local dev app health check on 127.0.0.1:15721\nTested: Local proxy logs showed cache_read_tokens after restart\nNot-tested: Full cargo test without local cc-switch port conflict\nRelated: #2217 * feat(proxy): aggregate forced Codex OAuth SSE into JSON for non-streaming clients Narrow override on top of #2235's streaming fallback. Codex OAuth always forces upstream openai_responses into SSE, even when the original Claude request is stream:false. #2235 handles this by routing such responses through the streaming transform so the client receives text/event-stream — that avoids the 422 that JSON parsing would produce, and it also protects any other provider that unexpectedly returns SSE (the response.is_sse() guard). But for Claude SDK callers that sent stream:false, returning SSE still violates the Anthropic non-streaming contract. This commit adds an override on exactly one combination — non-streaming client + codex_oauth + openai_responses — to aggregate the upstream Responses SSE into a synthetic Responses JSON and then run the regular responses_to_anthropic non-streaming transform. All other paths, including the generic response.is_sse() fallback, remain on the streaming path from #2235. The aggregator reuses proxy::sse::take_sse_block / strip_sse_field, which support both \n\n and \r\n\r\n delimiters; a hand-rolled split("\n\n") would silently fail on real HTTPS upstreams. Tests cover the happy path, CRLF delimiters, response.failed errors, and the missing response.completed defensive branch. --------- Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -57,6 +57,8 @@ pub struct RequestContext {
|
||||
pub app_type: AppType,
|
||||
/// Session ID(从客户端请求提取或新生成)
|
||||
pub session_id: String,
|
||||
/// Session ID 是否由客户端提供。生成的 UUID 不能作为上游缓存 key,否则每个请求都会换 key。
|
||||
pub session_client_provided: bool,
|
||||
/// 整流器配置
|
||||
pub rectifier_config: RectifierConfig,
|
||||
/// 优化器配置
|
||||
@@ -161,6 +163,7 @@ impl RequestContext {
|
||||
app_type_str,
|
||||
app_type,
|
||||
session_id,
|
||||
session_client_provided: session_result.client_provided,
|
||||
rectifier_config,
|
||||
optimizer_config,
|
||||
copilot_optimizer_config,
|
||||
@@ -223,6 +226,7 @@ impl RequestContext {
|
||||
state.app_handle.clone(),
|
||||
self.current_provider_id.clone(),
|
||||
self.session_id.clone(),
|
||||
self.session_client_provided,
|
||||
first_byte_timeout,
|
||||
idle_timeout,
|
||||
self.rectifier_config.clone(),
|
||||
|
||||
Reference in New Issue
Block a user