From 141978e115310daf630796a89e6b15c44c44a18f Mon Sep 17 00:00:00 2001 From: Clement Dumas Date: Thu, 15 Oct 2026 12:19:19 -0700 Subject: [PATCH 1/2] codex: run Claude Code's auto-review classifier like Codex's own reviewer On the auto-review route, default the classifier to Low reasoning effort and give it a reviewer-scoped prompt_cache_key (guardian:{session}), the two settings Codex uses for its approval reviewer (ext/guardian-reviewer/src/model.rs, core/src/guardian/review_session.rs). An explicit output_config.effort on the request still wins. Setting an effort turns on the reasoning summary and encrypted reasoning content. The classifier reads only its text verdict, so the route omits both. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Xmx9RE7u4ZLBzSUjpjqyBM --- src/anthropic/schema.rs | 4 ++ src/openai_compat/request.rs | 2 + src/providers/codex/translate/request.rs | 6 +- src/server.rs | 79 +++++++++++++++++++++++- 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/anthropic/schema.rs b/src/anthropic/schema.rs index 6f35b105..3b0a70d9 100644 --- a/src/anthropic/schema.rs +++ b/src/anthropic/schema.rs @@ -11,6 +11,10 @@ pub struct MessagesRequest { pub stream: bool, #[serde(skip)] pub bypass_provider_model_override: bool, + #[serde(skip)] + pub prompt_cache_key_override: Option, + #[serde(skip)] + pub omit_reasoning_artifacts: bool, #[serde(flatten)] pub extra: serde_json::Map, } diff --git a/src/openai_compat/request.rs b/src/openai_compat/request.rs index fac3aa6d..072a84d8 100644 --- a/src/openai_compat/request.rs +++ b/src/openai_compat/request.rs @@ -168,6 +168,8 @@ pub fn parse_request( messages, stream: true, bypass_provider_model_override: false, + prompt_cache_key_override: None, + omit_reasoning_artifacts: false, extra, }, requested_model, diff --git a/src/providers/codex/translate/request.rs b/src/providers/codex/translate/request.rs index fea9a779..4f40b5d9 100644 --- a/src/providers/codex/translate/request.rs +++ b/src/providers/codex/translate/request.rs @@ -579,6 +579,9 @@ fn translate_request_inner( if let Some(sid) = opts.session_id { out.prompt_cache_key = Some(sid); } + if let Some(key) = req.prompt_cache_key_override.clone() { + out.prompt_cache_key = Some(key); + } if apply_codex_config { let service_tier = resolve_service_tier(opts.service_tier, &out.model)?; @@ -600,6 +603,7 @@ fn translate_request_inner( let wants_reasoning = reasoning_requested(resolved_effort.as_ref()); if resolved_effort.is_some() || opts.use_responses_lite { let summary = if wants_reasoning + && !req.omit_reasoning_artifacts && (!apply_codex_config || reasoning_summary_requested(config::codex_reasoning_summary().as_deref())) { @@ -613,7 +617,7 @@ fn translate_request_inner( context: opts.use_responses_lite.then_some("all_turns".to_string()), }); } - if wants_reasoning { + if wants_reasoning && !req.omit_reasoning_artifacts { out.include = Some(vec!["reasoning.encrypted_content".to_string()]); } diff --git a/src/server.rs b/src/server.rs index a12db0c8..ea6d1171 100644 --- a/src/server.rs +++ b/src/server.rs @@ -107,6 +107,25 @@ fn apply_auto_review_model( Some(route) } +/// Mirrors Codex's own approval reviewer: Low effort (`ext/guardian-reviewer/src/model.rs`) +/// and a reviewer-scoped `guardian:{parent}` prompt cache key (`core/src/guardian/review_session.rs`). +fn apply_codex_auto_review_defaults( + body: &mut crate::anthropic::schema::MessagesRequest, + session_id: Option<&str>, +) { + let output_config = body + .extra + .entry("output_config") + .or_insert_with(|| json!({})); + if let Some(config) = output_config.as_object_mut() { + config.entry("effort").or_insert_with(|| json!("low")); + } + body.prompt_cache_key_override = session_id.map(|id| format!("guardian:{id}")); + // The classifier reads only its text verdict; a reasoning summary or encrypted + // reasoning would be generated and returned for nothing. + body.omit_reasoning_artifacts = true; +} + pub struct ServerConfig { pub bind_address: String, pub port: u16, @@ -1718,6 +1737,9 @@ async fn dispatch_request( }; body.bypass_provider_model_override = auto_review_route.is_some() && provider.name() == "codex"; + if body.bypass_provider_model_override { + apply_codex_auto_review_defaults(&mut body, session_id.as_deref()); + } if let Some(route) = auto_review_route.as_ref() { log.info( @@ -2318,7 +2340,10 @@ mod request_id_header_tests { #[cfg(test)] mod auto_review_tests { - use super::{apply_auto_review_model, headers_to_record, is_claude_auto_review_request}; + use super::{ + apply_auto_review_model, apply_codex_auto_review_defaults, headers_to_record, + is_claude_auto_review_request, + }; use crate::anthropic::schema::MessagesRequest; use crate::request_identity::{CLAUDE_AGENT_HEADER, CLAUDE_PARENT_AGENT_HEADER}; use http::{HeaderMap, HeaderValue}; @@ -2379,6 +2404,58 @@ mod auto_review_tests { assert_eq!(classifier.model.as_deref(), Some("gpt-6-luna")); } + #[test] + fn codex_classifier_gets_low_effort_and_guardian_cache_key() { + let mut classifier = request( + "You are a security monitor for autonomous AI coding agents.", + false, + json!([]), + ); + apply_codex_auto_review_defaults(&mut classifier, Some("sess-1")); + assert_eq!(classifier.extra["output_config"]["effort"], "low"); + assert_eq!( + classifier.prompt_cache_key_override.as_deref(), + Some("guardian:sess-1") + ); + let translated = crate::providers::codex::translate::request::translate_request( + &classifier, + crate::providers::codex::translate::request::TranslateOptions { + session_id: Some("sess-1".to_string()), + service_tier: None, + model: "gpt-6-luna".to_string(), + use_responses_lite: true, + }, + ) + .unwrap(); + assert_eq!( + translated.prompt_cache_key.as_deref(), + Some("guardian:sess-1") + ); + let reasoning = serde_json::to_value(&translated.reasoning).unwrap(); + assert_eq!(reasoning["effort"], "low"); + assert!( + reasoning + .get("summary") + .is_none_or(serde_json::Value::is_null) + ); + assert!(translated.include.is_none()); + } + + #[test] + fn codex_classifier_keeps_explicit_effort() { + let mut classifier = request( + "You are a security monitor for autonomous AI coding agents.", + false, + json!([]), + ); + classifier + .extra + .insert("output_config".to_string(), json!({"effort": "high"})); + apply_codex_auto_review_defaults(&mut classifier, None); + assert_eq!(classifier.extra["output_config"]["effort"], "high"); + assert!(classifier.prompt_cache_key_override.is_none()); + } + #[test] fn non_codex_classifier_keeps_requested_model_without_override() { let mut classifier = request( From 9733a017921aa391dae6163057af78ca5220ba21 Mon Sep 17 00:00:00 2001 From: Clement Dumas Date: Thu, 15 Oct 2026 12:40:05 -0700 Subject: [PATCH 2/2] docs: describe the Codex auto-review effort and cache key defaults Co-Authored-By: Claude Opus 5.5 --- docs/src/content/docs/providers/codex.md | 4 +++- docs/src/content/docs/reference/configuration.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/providers/codex.md b/docs/src/content/docs/providers/codex.md index cca0fc42..00ca81b7 100644 --- a/docs/src/content/docs/providers/codex.md +++ b/docs/src/content/docs/providers/codex.md @@ -54,6 +54,8 @@ Claude Code's `/effort` value maps to Codex `reasoning.effort`: `low`, `medium`, When reasoning is enabled, the proxy requests an automatic reasoning summary and translates summary deltas into Claude Code thinking blocks. Codex may omit a summary for a simple prompt. `CCP_CODEX_REASONING_SUMMARY=off` suppresses summaries while preserving effort and encrypted continuation content. +Detected auto-review classifier requests default to `low` effort, the effort Codex's own approval reviewer uses, unless the request already sets `output_config.effort`. + Claude Code summary compaction requests are capped at low effort by default because they perform extraction over a large transcript. `CCP_COMPACT_EFFORT=off` disables the cap, `none` removes reasoning, and another valid effort sets a different maximum. The cap never raises effort. ## Usage reporting @@ -117,7 +119,7 @@ WebSocket setup honors `HTTP_PROXY` for `ws://`, `HTTPS_PROXY` for the default ` An absent, malformed, or ambiguous identity does not reject the HTTP request; that request proceeds without continuation or WebSocket reuse. If the originating socket is missing, dead, or has been replaced, the proxy retries once with the full translated input and without the stale response ID. Continuation and connection state is held only in memory and is lost when the proxy restarts. -Detected auto-review classifier subrequests are intentionally stateless even when valid session and Agent headers are present. They neither consume nor publish continuation or WebSocket ownership. +Detected auto-review classifier subrequests are intentionally stateless even when valid session and Agent headers are present. They neither consume nor publish continuation or WebSocket ownership. Their `prompt_cache_key` is `guardian:`, the reviewer-scoped key Codex uses for its own approval reviewer, so they do not share the main conversation's cache key. ## Server compaction diff --git a/docs/src/content/docs/reference/configuration.md b/docs/src/content/docs/reference/configuration.md index 08514b10..8cd301a4 100644 --- a/docs/src/content/docs/reference/configuration.md +++ b/docs/src/content/docs/reference/configuration.md @@ -75,7 +75,7 @@ All keys are optional. An unreadable file, malformed JSON, or incompatible field `CCP_CONFIG_DIR` affects `config.json` and file-backed provider auth. It does not relocate the state directory. -Auto-review routing resolves the classifier's requested model first, not the main conversation's model. Recognized Claude aliases follow Codex/Kimi session affinity or `aliasProvider`; Grok and other providers do not establish alias affinity. Codex auto-review classifier requests use `gpt-6-luna` by default. Requests routed through other providers retain their requested model. `CCP_AUTO_REVIEW_MODEL` or `autoReviewModel` selects an explicit registered model for all detected classifier requests without changing the session's provider affinity. Normal messages, streaming requests, tool-using requests, and token counting retain their requested model. +Auto-review routing resolves the classifier's requested model first, not the main conversation's model. Recognized Claude aliases follow Codex/Kimi session affinity or `aliasProvider`; Grok and other providers do not establish alias affinity. Codex auto-review classifier requests use `gpt-6-luna` by default. Requests routed through other providers retain their requested model. `CCP_AUTO_REVIEW_MODEL` or `autoReviewModel` selects an explicit registered model for all detected classifier requests without changing the session's provider affinity. Normal messages, streaming requests, tool-using requests, and token counting retain their requested model. On Codex, detected classifier requests also default to `low` effort and use a `guardian:` prompt cache key, as Codex's own approval reviewer does. ## Outbound proxies