Skip to main content

claude_codex/
config.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use crate::paths;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum AliasProvider {
10    Anthropic,
11    Codex,
12    Kimi,
13}
14
15impl AliasProvider {
16    pub fn as_str(&self) -> &str {
17        match self {
18            AliasProvider::Anthropic => "anthropic",
19            AliasProvider::Codex => "codex",
20            AliasProvider::Kimi => "kimi",
21        }
22    }
23}
24
25#[derive(Debug, Clone)]
26pub struct LoadedConfig {
27    pub port: u16,
28    pub alias_provider: AliasProvider,
29    pub log_verbose: bool,
30    pub log_stderr: bool,
31    pub config_dir: PathBuf,
32}
33
34#[derive(Deserialize)]
35struct FileConfig {
36    pub port: Option<u16>,
37    #[serde(rename = "aliasProvider")]
38    pub alias_provider: Option<String>,
39    pub log: Option<FileLog>,
40    pub kimi: Option<KimiConfig>,
41    pub codex: Option<CodexConfig>,
42    pub cursor: Option<CursorConfig>,
43    pub grok: Option<GrokConfig>,
44}
45
46#[derive(Deserialize, Clone)]
47struct CodexConfig {
48    #[serde(rename = "baseUrl")]
49    pub base_url: Option<String>,
50    #[serde(rename = "originator")]
51    pub originator: Option<String>,
52    #[serde(rename = "userAgent")]
53    pub user_agent: Option<String>,
54    #[serde(rename = "previousResponseId")]
55    pub previous_response_id: Option<bool>,
56    #[serde(rename = "serviceTier")]
57    pub service_tier: Option<String>,
58    #[serde(rename = "reasoningSummary")]
59    pub reasoning_summary: Option<String>,
60    #[serde(rename = "effort")]
61    pub effort: Option<String>,
62    #[serde(rename = "model")]
63    pub model: Option<String>,
64    pub transport: Option<String>,
65}
66
67#[derive(Deserialize, Clone)]
68struct CursorConfig {
69    #[serde(rename = "baseUrl")]
70    pub base_url: Option<String>,
71    #[serde(rename = "clientVersion")]
72    pub client_version: Option<String>,
73    #[serde(rename = "agentBundle")]
74    pub agent_bundle: Option<String>,
75}
76
77#[derive(Deserialize, Clone)]
78struct KimiConfig {
79    #[serde(rename = "userAgent")]
80    pub user_agent: Option<String>,
81    #[serde(rename = "oauthHost")]
82    pub oauth_host: Option<String>,
83    #[serde(rename = "baseUrl")]
84    pub base_url: Option<String>,
85}
86
87#[derive(Deserialize, Clone)]
88struct GrokConfig {
89    #[serde(rename = "baseUrl")]
90    pub base_url: Option<String>,
91    #[serde(rename = "clientVersion")]
92    pub client_version: Option<String>,
93}
94
95#[derive(Deserialize)]
96struct FileLog {
97    pub verbose: Option<bool>,
98    pub stderr: Option<bool>,
99}
100
101fn parse_alias(raw: &str) -> Option<AliasProvider> {
102    match raw {
103        "anthropic" => Some(AliasProvider::Anthropic),
104        "codex" => Some(AliasProvider::Codex),
105        "kimi" => Some(AliasProvider::Kimi),
106        _ => None,
107    }
108}
109
110fn read_file_config(config_dir: &Path) -> Option<FileConfig> {
111    let path = config_dir.join("config.json");
112    let raw = fs::read_to_string(path).ok()?;
113    serde_json::from_str(&raw).ok()
114}
115
116pub fn load_config() -> LoadedConfig {
117    let config_dir = paths::config_dir();
118    let file = read_file_config(&config_dir);
119    let env: HashMap<_, _> = std::env::vars().collect();
120
121    let mut out = LoadedConfig {
122        port: 18765,
123        alias_provider: AliasProvider::Anthropic,
124        log_verbose: false,
125        log_stderr: false,
126        config_dir: config_dir.clone(),
127    };
128
129    if let Some(raw) = env.get("CCP_ALIAS_PROVIDER") {
130        if let Some(alias) = parse_alias(raw) {
131            out.alias_provider = alias;
132        }
133    } else if let Some(alias_provider) = file
134        .as_ref()
135        .and_then(|f| f.alias_provider.as_deref())
136        .and_then(parse_alias)
137    {
138        out.alias_provider = alias_provider;
139    }
140
141    if let Some(raw) = env.get("PORT") {
142        if let Ok(port) = raw.parse::<u16>() {
143            out.port = port;
144        }
145    } else if let Some(port) = file.as_ref().and_then(|f| f.port) {
146        out.port = port;
147    }
148
149    if env.contains_key("CCP_LOG_VERBOSE") {
150        out.log_verbose = true;
151    } else if let Some(value) = file
152        .as_ref()
153        .and_then(|f| f.log.as_ref().and_then(|v| v.verbose))
154    {
155        out.log_verbose = value;
156    }
157
158    if env.contains_key("CCP_LOG_STDERR") {
159        out.log_stderr = true;
160    } else if let Some(value) = file
161        .as_ref()
162        .and_then(|f| f.log.as_ref().and_then(|v| v.stderr))
163    {
164        out.log_stderr = value;
165    }
166
167    out
168}
169
170pub fn config_path() -> PathBuf {
171    paths::config_dir().join("config.json")
172}
173
174pub fn port() -> u16 {
175    load_config().port
176}
177
178pub fn alias_provider() -> AliasProvider {
179    load_config().alias_provider
180}
181
182pub fn log_verbose() -> bool {
183    load_config().log_verbose
184}
185
186pub fn log_stderr() -> bool {
187    load_config().log_stderr
188}
189
190pub fn config_override_summary_lines(cfg: &LoadedConfig) -> Vec<String> {
191    let file = read_file_config(&cfg.config_dir);
192    let env: HashMap<_, _> = std::env::vars().collect();
193    let mut out = Vec::new();
194    if env.contains_key("PORT") {
195        out.push("port (env)".to_string());
196    }
197    if env.contains_key("CCP_ALIAS_PROVIDER") {
198        out.push("aliasProvider (env)".to_string());
199    }
200    if env.contains_key("CCP_LOG_VERBOSE") {
201        out.push("log.verbose (env)".to_string());
202    }
203    if env.contains_key("CCP_LOG_STDERR") {
204        out.push("log.stderr (env)".to_string());
205    }
206    if env.contains_key("CCP_KIMI_OAUTH_HOST") {
207        out.push("kimi.oauthHost (env)".to_string());
208    }
209    if env.contains_key("CCP_KIMI_BASE_URL") {
210        out.push("kimi.baseUrl (env)".to_string());
211    }
212    if env.contains_key("CCP_CURSOR_BASE_URL") {
213        out.push("cursor.baseUrl (env)".to_string());
214    }
215    if env.contains_key("CCP_CURSOR_CLIENT_VERSION") {
216        out.push("cursor.clientVersion (env)".to_string());
217    }
218    if env.contains_key("CCP_KIMI_USER_AGENT") {
219        out.push("kimi.userAgent (env)".to_string());
220    }
221    if env.contains_key("CCP_GROK_BASE_URL") {
222        out.push("grok.baseUrl (env)".to_string());
223    }
224    if env.contains_key("CCP_GROK_CLIENT_VERSION") {
225        out.push("grok.clientVersion (env)".to_string());
226    }
227    if env
228        .get("CCP_CODEX_REASONING_SUMMARY")
229        .is_some_and(|raw| !raw.is_empty())
230    {
231        out.push("CCP_CODEX_REASONING_SUMMARY (env)".to_string());
232    }
233    if let Some(file_cfg) = file {
234        if let Some(p) = file_cfg.port {
235            out.push(format!("port: {p}"));
236        }
237        if let Some(alias) = file_cfg.alias_provider {
238            out.push(format!("aliasProvider: {alias}"));
239        }
240        if let Some(log) = file_cfg.log {
241            if let Some(v) = log.verbose {
242                out.push(format!("log.verbose: {v}"));
243            }
244            if let Some(v) = log.stderr {
245                out.push(format!("log.stderr: {v}"));
246            }
247        }
248        if let Some(codex) = file_cfg.codex
249            && let Some(summary) = codex.reasoning_summary
250            && !summary.is_empty()
251        {
252            out.push("codex.reasoningSummary (config)".to_string());
253        }
254    }
255    out
256}
257
258pub fn grok_base_url() -> String {
259    let env: HashMap<_, _> = std::env::vars().collect();
260    if let Some(raw) = env.get("CCP_GROK_BASE_URL") {
261        return raw.clone();
262    }
263    if let Some(grok) = read_file_config(&paths::config_dir()).and_then(|f| f.grok)
264        && let Some(url) = grok.base_url
265    {
266        return url;
267    }
268    "https://cli-chat-proxy.grok.com/v1".to_string()
269}
270
271pub fn grok_client_version() -> String {
272    let env: HashMap<_, _> = std::env::vars().collect();
273    if let Some(raw) = env.get("CCP_GROK_CLIENT_VERSION") {
274        return raw.clone();
275    }
276    if let Some(grok) = read_file_config(&paths::config_dir()).and_then(|f| f.grok)
277        && let Some(version) = grok.client_version
278    {
279        return version;
280    }
281    "0.2.93".to_string()
282}
283
284pub fn is_verbose() -> bool {
285    log_verbose()
286}
287
288pub fn anthropic_base_url() -> String {
289    let env: HashMap<_, _> = std::env::vars().collect();
290    if let Some(raw) = env.get("CCP_ANTHROPIC_BASE_URL") {
291        return raw.clone();
292    }
293    "https://api.anthropic.com".to_string()
294}
295
296pub fn kimi_oauth_host() -> String {
297    let env: HashMap<_, _> = std::env::vars().collect();
298    if let Some(raw) = env.get("CCP_KIMI_OAUTH_HOST") {
299        return raw.clone();
300    }
301    let config_dir = paths::config_dir();
302    if let Some(file) = read_file_config(&config_dir)
303        && let Some(kimi) = file.kimi
304        && let Some(host) = kimi.oauth_host
305    {
306        return host;
307    }
308    "https://auth.kimi.com".to_string()
309}
310
311pub fn kimi_base_url() -> String {
312    let env: HashMap<_, _> = std::env::vars().collect();
313    if let Some(raw) = env.get("CCP_KIMI_BASE_URL") {
314        return raw.clone();
315    }
316    let config_dir = paths::config_dir();
317    if let Some(file) = read_file_config(&config_dir)
318        && let Some(kimi) = file.kimi
319        && let Some(url) = kimi.base_url
320    {
321        return url;
322    }
323    "https://api.kimi.com/coding/v1".to_string()
324}
325
326pub fn kimi_user_agent(default: &str) -> String {
327    let env: HashMap<_, _> = std::env::vars().collect();
328    if let Some(raw) = env.get("CCP_KIMI_USER_AGENT") {
329        return raw.clone();
330    }
331    if let Some(raw) = env.get("CCP_USER_AGENT") {
332        return raw.clone();
333    }
334    let config_dir = paths::config_dir();
335    if let Some(file) = read_file_config(&config_dir)
336        && let Some(kimi) = file.kimi
337        && let Some(ua) = kimi.user_agent
338    {
339        return ua;
340    }
341    default.to_string()
342}
343
344// ---------------------------------------------------------------------------
345// Codex config
346// ---------------------------------------------------------------------------
347
348pub fn codex_base_url(default: &str) -> String {
349    let env: HashMap<_, _> = std::env::vars().collect();
350    if let Some(raw) = env.get("CCP_CODEX_BASE_URL") {
351        return raw.clone();
352    }
353    if let Some(raw) = env.get("CLAUDE_CODE_PROXY_CODEX_BASE_URL") {
354        return raw.clone();
355    }
356    let config_dir = paths::config_dir();
357    if let Some(file) = read_file_config(&config_dir)
358        && let Some(codex) = file.codex
359        && let Some(url) = codex.base_url
360    {
361        return url;
362    }
363    default.to_string()
364}
365
366pub fn codex_originator(default: &str) -> String {
367    let env: HashMap<_, _> = std::env::vars().collect();
368    if let Some(raw) = env.get("CCP_CODEX_ORIGINATOR") {
369        return raw.clone();
370    }
371    let config_dir = paths::config_dir();
372    if let Some(file) = read_file_config(&config_dir)
373        && let Some(codex) = file.codex
374        && let Some(val) = codex.originator
375    {
376        return val;
377    }
378    default.to_string()
379}
380
381pub fn codex_user_agent(default: &str) -> String {
382    let env: HashMap<_, _> = std::env::vars().collect();
383    if let Some(raw) = env.get("CCP_CODEX_USER_AGENT") {
384        return raw.clone();
385    }
386    if let Some(raw) = env.get("CCP_USER_AGENT") {
387        return raw.clone();
388    }
389    let config_dir = paths::config_dir();
390    if let Some(file) = read_file_config(&config_dir)
391        && let Some(codex) = file.codex
392        && let Some(ua) = codex.user_agent
393    {
394        return ua;
395    }
396    default.to_string()
397}
398
399pub fn codex_previous_response_id() -> bool {
400    let env: HashMap<_, _> = std::env::vars().collect();
401    if let Some(raw) = env.get("CCP_CODEX_PREVIOUS_RESPONSE_ID") {
402        return matches!(raw.to_ascii_lowercase().as_str(), "1" | "true" | "yes");
403    }
404    let config_dir = paths::config_dir();
405    if let Some(file) = read_file_config(&config_dir)
406        && let Some(codex) = file.codex
407        && let Some(val) = codex.previous_response_id
408    {
409        return val;
410    }
411    false
412}
413
414pub fn codex_service_tier() -> Option<String> {
415    let env: HashMap<_, _> = std::env::vars().collect();
416    if let Some(raw) = env.get("CCP_CODEX_SERVICE_TIER") {
417        return Some(raw.clone());
418    }
419    let config_dir = paths::config_dir();
420    if let Some(file) = read_file_config(&config_dir)
421        && let Some(codex) = file.codex
422    {
423        return codex.service_tier;
424    }
425    None
426}
427
428pub fn codex_effort() -> Option<String> {
429    let env: HashMap<_, _> = std::env::vars().collect();
430    if let Some(raw) = env.get("CCP_CODEX_EFFORT") {
431        return Some(raw.clone());
432    }
433    let config_dir = paths::config_dir();
434    if let Some(file) = read_file_config(&config_dir)
435        && let Some(codex) = file.codex
436    {
437        return codex.effort;
438    }
439    None
440}
441
442pub fn codex_reasoning_summary() -> Option<String> {
443    let env: HashMap<_, _> = std::env::vars().collect();
444    if let Some(raw) = env
445        .get("CCP_CODEX_REASONING_SUMMARY")
446        .filter(|raw| !raw.is_empty())
447    {
448        return Some(raw.clone());
449    }
450    let config_dir = paths::config_dir();
451    if let Some(file) = read_file_config(&config_dir)
452        && let Some(codex) = file.codex
453        && let Some(summary) = codex.reasoning_summary.filter(|raw| !raw.is_empty())
454    {
455        return Some(summary);
456    }
457    None
458}
459
460pub fn codex_model() -> Option<String> {
461    let env: HashMap<_, _> = std::env::vars().collect();
462    if let Some(raw) = env.get("CCP_CODEX_MODEL") {
463        return Some(raw.clone());
464    }
465    let config_dir = paths::config_dir();
466    if let Some(file) = read_file_config(&config_dir)
467        && let Some(codex) = file.codex
468    {
469        return codex.model;
470    }
471    None
472}
473
474// ---------------------------------------------------------------------------
475// Codex transport config
476// ---------------------------------------------------------------------------
477
478#[derive(Debug, Clone, Copy, PartialEq, Eq)]
479pub enum CodexTransport {
480    Http,
481    WebSocket,
482    Auto,
483}
484
485impl CodexTransport {
486    pub fn as_str(self) -> &'static str {
487        match self {
488            CodexTransport::Http => "http",
489            CodexTransport::WebSocket => "websocket",
490            CodexTransport::Auto => "auto",
491        }
492    }
493}
494
495fn parse_codex_transport(raw: &str) -> Option<CodexTransport> {
496    match raw {
497        "http" => Some(CodexTransport::Http),
498        "websocket" => Some(CodexTransport::WebSocket),
499        "auto" => Some(CodexTransport::Auto),
500        _ => None,
501    }
502}
503
504pub fn codex_transport() -> CodexTransport {
505    let env: HashMap<_, _> = std::env::vars().collect();
506    if let Some(raw) = env.get("CCP_CODEX_TRANSPORT")
507        && let Some(transport) = parse_codex_transport(raw)
508    {
509        return transport;
510    }
511    let config_dir = paths::config_dir();
512    if let Some(file) = read_file_config(&config_dir)
513        && let Some(codex) = file.codex
514        && let Some(transport) = codex.transport.as_deref().and_then(parse_codex_transport)
515    {
516        return transport;
517    }
518    CodexTransport::WebSocket
519}
520
521// ---------------------------------------------------------------------------
522// Cursor config
523// ---------------------------------------------------------------------------
524
525pub fn cursor_base_url() -> String {
526    let env: HashMap<_, _> = std::env::vars().collect();
527    if let Some(raw) = env.get("CCP_CURSOR_BASE_URL") {
528        return raw.clone();
529    }
530    let config_dir = paths::config_dir();
531    if let Some(file) = read_file_config(&config_dir)
532        && let Some(cursor) = file.cursor
533        && let Some(url) = cursor.base_url
534    {
535        return url;
536    }
537    "https://api2.cursor.sh".to_string()
538}
539
540pub fn cursor_client_version() -> String {
541    let env: HashMap<_, _> = std::env::vars().collect();
542    if let Some(raw) = env.get("CCP_CURSOR_CLIENT_VERSION") {
543        return raw.clone();
544    }
545    let config_dir = paths::config_dir();
546    if let Some(file) = read_file_config(&config_dir)
547        && let Some(cursor) = file.cursor
548        && let Some(version) = cursor.client_version
549    {
550        return version;
551    }
552    "0.48.5".to_string()
553}
554
555pub fn cursor_agent_bundle() -> Option<String> {
556    let env: HashMap<_, _> = std::env::vars().collect();
557    if let Some(raw) = env.get("CCP_CURSOR_AGENT_BUNDLE") {
558        return Some(raw.clone());
559    }
560    let config_dir = paths::config_dir();
561    if let Some(file) = read_file_config(&config_dir)
562        && let Some(cursor) = file.cursor
563        && let Some(bundle) = cursor.agent_bundle
564    {
565        return Some(bundle);
566    }
567    None
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573    use once_cell::sync::Lazy;
574    use std::sync::Mutex;
575
576    static ENV_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
577
578    fn clear_env() {
579        unsafe {
580            std::env::remove_var("CCP_CODEX_TRANSPORT");
581            std::env::remove_var("CCP_CONFIG_DIR");
582            std::env::remove_var("CCP_LOG_VERBOSE");
583            std::env::remove_var("CCP_LOG_STDERR");
584            std::env::remove_var("CCP_CODEX_REASONING_SUMMARY");
585        }
586    }
587
588    struct EnvGuard {
589        key: &'static str,
590        previous: Option<std::ffi::OsString>,
591    }
592
593    impl EnvGuard {
594        fn set(key: &'static str, value: impl AsRef<std::ffi::OsStr>) -> Self {
595            let previous = std::env::var_os(key);
596            unsafe {
597                std::env::set_var(key, value);
598            }
599            Self { key, previous }
600        }
601    }
602
603    impl Drop for EnvGuard {
604        fn drop(&mut self) {
605            unsafe {
606                match self.previous.take() {
607                    Some(value) => std::env::set_var(self.key, value),
608                    None => std::env::remove_var(self.key),
609                }
610            }
611        }
612    }
613
614    #[test]
615    fn codex_transport_defaults_to_websocket() {
616        let _guard = ENV_LOCK.lock().unwrap();
617        clear_env();
618        let result = codex_transport();
619        assert_eq!(result, CodexTransport::WebSocket);
620    }
621
622    #[test]
623    fn codex_transport_reads_env() {
624        let _guard = ENV_LOCK.lock().unwrap();
625        clear_env();
626        unsafe {
627            std::env::set_var("CCP_CODEX_TRANSPORT", "auto");
628        }
629        assert_eq!(codex_transport(), CodexTransport::Auto);
630    }
631
632    #[test]
633    fn codex_transport_env_websocket() {
634        let _guard = ENV_LOCK.lock().unwrap();
635        clear_env();
636        unsafe {
637            std::env::set_var("CCP_CODEX_TRANSPORT", "websocket");
638        }
639        assert_eq!(codex_transport(), CodexTransport::WebSocket);
640    }
641
642    #[test]
643    fn codex_transport_invalid_env_falls_back_to_websocket() {
644        let _guard = ENV_LOCK.lock().unwrap();
645        clear_env();
646        unsafe {
647            std::env::set_var("CCP_CODEX_TRANSPORT", "invalid");
648        }
649        assert_eq!(codex_transport(), CodexTransport::WebSocket);
650    }
651
652    #[test]
653    fn codex_transport_empty_env_falls_back_to_websocket() {
654        let _guard = ENV_LOCK.lock().unwrap();
655        clear_env();
656        unsafe {
657            std::env::set_var("CCP_CODEX_TRANSPORT", "");
658        }
659        assert_eq!(codex_transport(), CodexTransport::WebSocket);
660    }
661
662    #[test]
663    fn parse_codex_transport_variants() {
664        assert_eq!(parse_codex_transport("http"), Some(CodexTransport::Http));
665        assert_eq!(
666            parse_codex_transport("websocket"),
667            Some(CodexTransport::WebSocket)
668        );
669        assert_eq!(parse_codex_transport("auto"), Some(CodexTransport::Auto));
670        assert_eq!(parse_codex_transport(""), None);
671        assert_eq!(parse_codex_transport("HTTP"), None);
672        assert_eq!(parse_codex_transport("ws"), None);
673    }
674
675    #[test]
676    fn codex_transport_as_str() {
677        assert_eq!(CodexTransport::Http.as_str(), "http");
678        assert_eq!(CodexTransport::WebSocket.as_str(), "websocket");
679        assert_eq!(CodexTransport::Auto.as_str(), "auto");
680    }
681
682    #[test]
683    fn log_env_presence_enables_legacy_verbose_and_stderr() {
684        let _guard = ENV_LOCK.lock().unwrap();
685        clear_env();
686        let config = tempfile::TempDir::new().unwrap();
687        let _config_env = EnvGuard::set("CCP_CONFIG_DIR", config.path());
688        let _verbose_env = EnvGuard::set("CCP_LOG_VERBOSE", "0");
689        let _stderr_env = EnvGuard::set("CCP_LOG_STDERR", "");
690
691        let loaded = load_config();
692        assert!(loaded.log_verbose);
693        assert!(loaded.log_stderr);
694    }
695
696    #[test]
697    fn log_config_values_apply_without_env() {
698        let _guard = ENV_LOCK.lock().unwrap();
699        clear_env();
700        let config = tempfile::TempDir::new().unwrap();
701        std::fs::write(
702            config.path().join("config.json"),
703            r#"{"log":{"verbose":true,"stderr":true}}"#,
704        )
705        .unwrap();
706        let _config_env = EnvGuard::set("CCP_CONFIG_DIR", config.path());
707
708        let loaded = load_config();
709        assert!(loaded.log_verbose);
710        assert!(loaded.log_stderr);
711    }
712
713    #[test]
714    fn codex_reasoning_summary_reads_config() {
715        let _guard = ENV_LOCK.lock().unwrap();
716        clear_env();
717        let config = tempfile::TempDir::new().unwrap();
718        std::fs::write(
719            config.path().join("config.json"),
720            r#"{"codex":{"reasoningSummary":"off"}}"#,
721        )
722        .unwrap();
723        let _config_env = EnvGuard::set("CCP_CONFIG_DIR", config.path());
724
725        assert_eq!(codex_reasoning_summary().as_deref(), Some("off"));
726    }
727
728    #[test]
729    fn codex_reasoning_summary_env_overrides_config_and_empty_falls_through() {
730        let _guard = ENV_LOCK.lock().unwrap();
731        clear_env();
732        let config = tempfile::TempDir::new().unwrap();
733        std::fs::write(
734            config.path().join("config.json"),
735            r#"{"codex":{"reasoningSummary":"off"}}"#,
736        )
737        .unwrap();
738        let _config_env = EnvGuard::set("CCP_CONFIG_DIR", config.path());
739        {
740            let _summary_env = EnvGuard::set("CCP_CODEX_REASONING_SUMMARY", "auto");
741            assert_eq!(codex_reasoning_summary().as_deref(), Some("auto"));
742        }
743        {
744            let _summary_env = EnvGuard::set("CCP_CODEX_REASONING_SUMMARY", "");
745            assert_eq!(codex_reasoning_summary().as_deref(), Some("off"));
746        }
747    }
748}