From c41a72cd3f5886a6b367e93e28c4ef0662a54500 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 1 Oct 2026 04:02:38 +0000 Subject: [PATCH] Support Daybreak selection in `codex exec` (#49856) ## What changed - Add the `daybreak` configuration key, defaulting to `false`, with per-invocation overrides such as `-c daybreak=true`. - Resolve access programs from the model catalog, preferring `daybreak_blue` over `daybreak_red` when enabled and selecting advertised `standard` access when disabled for signed-in ChatGPT accounts. Require the OpenAI provider, a signed-in ChatGPT account, and a compatible model to enable Daybreak. - Save the choice for new persistent threads and inherit it on resume and fork, including ephemeral forks. Explicit CLI overrides apply only to the current invocation. ## Testing Add protocol tests for advertised-program selection and exec integration tests for account and provider restrictions, saved choices, ephemeral forks, and invocation-only overrides. GitOrigin-RevId: ac955f08b5a891e720cfc5f67fabb7b0f6d32bb8 --- codex-rs/config/src/config_toml.rs | 2 + codex-rs/core/config.schema.json | 4 + codex-rs/core/src/config/mod.rs | 4 + codex-rs/exec/src/daybreak.rs | 96 +++++++ codex-rs/exec/src/lib.rs | 55 +++- codex-rs/exec/tests/suite/daybreak.rs | 268 ++++++++++++++++++ codex-rs/exec/tests/suite/mod.rs | 1 + .../src/openai_models/access_programs.rs | 23 ++ .../openai_models/access_programs_tests.rs | 24 ++ codex-rs/thread-manager-sample/src/main.rs | 1 + 10 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 codex-rs/exec/src/daybreak.rs create mode 100644 codex-rs/exec/tests/suite/daybreak.rs create mode 100644 codex-rs/protocol/src/openai_models/access_programs_tests.rs diff --git a/codex-rs/config/src/config_toml.rs b/codex-rs/config/src/config_toml.rs index 7bcb3ef853..3e5cc07e6c 100644 --- a/codex-rs/config/src/config_toml.rs +++ b/codex-rs/config/src/config_toml.rs @@ -166,6 +166,8 @@ pub struct FeatureToggleToml { pub struct ConfigToml { /// Optional override of model selection. pub model: Option, + /// Default Daybreak preference for new threads and non-interactive turns. + pub daybreak: Option, /// Review model override used by the `/review` feature. pub review_model: Option, diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 02e2acd869..570f0319bd 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -6742,6 +6742,10 @@ "computer_use": { "$ref": "#/definitions/ComputerUseConfigToml" }, + "daybreak": { + "description": "Default Daybreak preference for new threads and non-interactive turns.", + "type": "boolean" + }, "default_permissions": { "description": "Default permissions profile to apply. Names starting with `:` refer to built-in profiles; other names are resolved from the `[permissions]` table.", "type": "string" diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 9fc3ffe288..fe06279954 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -623,6 +623,9 @@ pub struct Config { /// Optional override of model selection. pub model: Option, + /// Default Daybreak preference for new threads and non-interactive turns. + pub daybreak_enabled: bool, + /// Effective service tier request id preference for new turns. /// `default` means the user explicitly selected standard routing. pub service_tier: Option, @@ -4282,6 +4285,7 @@ impl Config { let config = Self { prefer_mxc, model, + daybreak_enabled: cfg.daybreak.unwrap_or(false), service_tier, review_model, model_context_window: cfg.model_context_window, diff --git a/codex-rs/exec/src/daybreak.rs b/codex-rs/exec/src/daybreak.rs new file mode 100644 index 0000000000..823a0437b8 --- /dev/null +++ b/codex-rs/exec/src/daybreak.rs @@ -0,0 +1,96 @@ +//! Resolve the cyber treatment for an exec turn from the connected server's catalog. + +use crate::RequestIdSequencer; +use codex_app_server_client::InProcessAppServerClient; +use codex_app_server_protocol::Account; +use codex_app_server_protocol::ClientRequest; +use codex_app_server_protocol::CyberAccessProgram; +use codex_app_server_protocol::GetAccountParams; +use codex_app_server_protocol::GetAccountResponse; +use codex_app_server_protocol::ModelListParams; +use codex_app_server_protocol::ModelListResponse; +use codex_protocol::openai_models::ModelAccessPrograms; + +pub(crate) async fn program_for_turn( + client: &InProcessAppServerClient, + request_ids: &mut RequestIdSequencer, + model: &str, + provider: &str, + enabled: bool, +) -> anyhow::Result> { + if provider != "openai" { + anyhow::ensure!(!enabled, "Daybreak requires the OpenAI model provider"); + return Ok(None); + } + + if enabled { + anyhow::ensure!( + has_chatgpt_account(client, request_ids).await?, + "Daybreak requires a signed-in ChatGPT account" + ); + } + + let mut cursor = None; + loop { + let result = client + .request_typed::(ClientRequest::ModelList { + request_id: request_ids.next(), + params: ModelListParams { + cursor, + limit: None, + include_hidden: Some(true), + }, + }) + .await; + let Ok(result) = result else { + anyhow::ensure!(!enabled, "Daybreak availability could not be determined"); + return Ok(None); + }; + if let Some(selected) = result.data.into_iter().find(|entry| entry.model == model) { + let programs = selected + .available_access_programs + .map(ModelAccessPrograms::from); + if enabled { + return programs + .and_then(|programs| programs.daybreak()) + .map(|program| Some(program.into())) + .ok_or_else(|| anyhow::anyhow!("Daybreak is unavailable for model {model}; turn it off or choose a compatible model")); + } + let Some(standard) = programs.and_then(|programs| programs.standard()) else { + return Ok(None); + }; + return Ok(has_chatgpt_account(client, request_ids) + .await + .unwrap_or(false) + .then_some(standard.into())); + } + match result.next_cursor { + Some(next) => cursor = Some(next), + None => { + anyhow::ensure!( + !enabled, + "Daybreak availability could not be determined for model {model}" + ); + return Ok(None); + } + } + } +} + +async fn has_chatgpt_account( + client: &InProcessAppServerClient, + request_ids: &mut RequestIdSequencer, +) -> anyhow::Result { + let account = client + .request_typed::(ClientRequest::GetAccount { + request_id: request_ids.next(), + params: GetAccountParams { + refresh_token: false, + }, + }) + .await + .map_err(|error| { + anyhow::anyhow!("Daybreak account availability could not be determined: {error}") + })?; + Ok(matches!(account.account, Some(Account::Chatgpt { .. }))) +} diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 0bfa07bd55..6eddf37370 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -6,6 +6,7 @@ #![deny(clippy::print_stdout)] mod cli; +mod daybreak; mod event_processor; mod event_processor_with_human_output; pub(crate) mod event_processor_with_jsonl_output; @@ -218,6 +219,7 @@ struct ExecRunArgs { command: Option, config: Config, resume_approvals_reviewer_override: Option, + daybreak_override: Option, dangerously_bypass_approvals_and_sandbox: bool, exec_span: tracing::Span, images: Vec, @@ -615,6 +617,10 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result ) .await?; embedded_network_policy.activate(&mut config); + let daybreak_override = cli_kv_overrides + .iter() + .any(|(key, _)| key == "daybreak") + .then_some(config.daybreak_enabled); let resume_approvals_reviewer_override = cli_kv_overrides .iter() .any(|(key, _)| key == "approvals_reviewer") @@ -737,6 +743,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result command, config, resume_approvals_reviewer_override, + daybreak_override, dangerously_bypass_approvals_and_sandbox, exec_span: exec_span.clone(), images, @@ -837,6 +844,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { command, config, resume_approvals_reviewer_override, + daybreak_override, dangerously_bypass_approvals_and_sandbox, exec_span, images, @@ -852,6 +860,16 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { thread_source, } = args; + if config.daybreak_enabled && !matches!(&command, Some(ExecCommand::Review(_))) { + anyhow::ensure!( + !oss || matches!( + &command, + Some(ExecCommand::Resume(_) | ExecCommand::Fork(_)) + ), + "Daybreak requires the OpenAI model provider" + ); + } + let mut event_processor: Box = match json_mode { true => Box::new(EventProcessorWithJsonOutput::new(last_message_file.clone())), _ => Box::new(EventProcessorWithHumanOutput::create_with_ansi( @@ -989,6 +1007,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { anyhow::anyhow!("failed to initialize in-process app-server client: {err}") })?; + let mut daybreak_enabled = config.daybreak_enabled; // Resolve resume and fork through existing app-server thread lifecycle APIs. let (primary_thread_id, fallback_session_configured) = if let Some(ExecCommand::Resume(args)) = command.as_ref() @@ -1013,6 +1032,9 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { let session_configured = session_configured_from_thread_resume_response(&response, &config) .map_err(anyhow::Error::msg)?; + daybreak_enabled = daybreak_override + .or(response.thread.daybreak_enabled) + .unwrap_or(false); (session_configured.thread_id, session_configured) } else { let response = start_thread(&client, &mut request_ids, &config, &thread_source) @@ -1035,6 +1057,24 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { resolve_resume_thread_id(&client, &config, state_db.as_ref(), &source_args) .await? .ok_or_else(|| anyhow::anyhow!("Session not found: {}", args.session_id))?; + let source_daybreak = if config.ephemeral && daybreak_override.is_none() { + let response: ThreadReadResponse = send_request_with_response( + &client, + ClientRequest::ThreadRead { + request_id: request_ids.next(), + params: ThreadReadParams { + thread_id: source_thread_id.clone(), + include_turns: false, + }, + }, + "thread/read", + ) + .await + .map_err(anyhow::Error::msg)?; + response.thread.daybreak_enabled + } else { + None + }; let permissions = permissions_selection_from_config(&config); let sandbox = permissions.is_none().then(|| { sandbox_mode_from_permission_profile( @@ -1087,6 +1127,10 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { response.reasoning_effort, ) .map_err(anyhow::Error::msg)?; + daybreak_enabled = daybreak_override + .or(response.thread.daybreak_enabled) + .or(source_daybreak) + .unwrap_or(false); (session_configured.thread_id, session_configured) } else { let response = start_thread(&client, &mut request_ids, &config, &thread_source) @@ -1147,6 +1191,14 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { items, output_schema, } => { + let cyber_access_program = daybreak::program_for_turn( + &client, + &mut request_ids, + &session_configured.model, + &session_configured.model_provider_id, + daybreak_enabled, + ) + .await?; let response: TurnStartResponse = send_request_with_response( &client, ClientRequest::TurnStart { @@ -1176,7 +1228,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> { output_schema, collaboration_mode: None, multi_agent_mode: None, - cyber_access_program: None, + cyber_access_program, }, }, "turn/start", @@ -1377,6 +1429,7 @@ fn thread_start_params_from_config( permissions, config: thread_config_overrides_from_config(config), ephemeral: Some(config.ephemeral), + daybreak_enabled: (config.daybreak_enabled && !config.ephemeral).then_some(true), history_mode: (!config.ephemeral).then_some(ThreadHistoryMode::Paginated), thread_source: Some(thread_source.clone()), ..ThreadStartParams::default() diff --git a/codex-rs/exec/tests/suite/daybreak.rs b/codex-rs/exec/tests/suite/daybreak.rs new file mode 100644 index 0000000000..0b03c8dabc --- /dev/null +++ b/codex-rs/exec/tests/suite/daybreak.rs @@ -0,0 +1,268 @@ +use anyhow::Context; +use core_test_support::responses; +use core_test_support::test_codex_exec::TestCodexExecBuilder; +use core_test_support::test_codex_exec::test_codex_exec; +use predicates::prelude::*; +use pretty_assertions::assert_eq; +use serde_json::json; + +fn started_thread(output: std::process::Output) -> anyhow::Result { + assert!( + output.status.success(), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + String::from_utf8(output.stdout)? + .lines() + .filter_map(|line| serde_json::from_str::(line).ok()) + .find(|event| event["type"] == "thread.started") + .and_then(|event| event["thread_id"].as_str().map(str::to_owned)) + .context("exec should report its thread ID") +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_rejects_daybreak_for_api_keys_and_allows_an_override() -> anyhow::Result<()> { + let (test, server) = configured_daybreak_exec().await?; + std::fs::remove_file(test.home_path().join("auth.json"))?; + let response = responses::mount_sse_once(&server, responses::sse_completed("response")).await; + + test.cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .arg("--skip-git-repo-check") + .arg("hello") + .assert() + .failure() + .stderr(predicate::str::contains( + "Daybreak requires a signed-in ChatGPT account", + )); + assert!(response.requests().is_empty()); + + test.cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .arg("--skip-git-repo-check") + .arg("-c") + .arg("daybreak=false") + .arg("hello") + .assert() + .success(); + assert_eq!( + response.single_request().body_json().get("access_programs"), + None + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_rejects_daybreak_for_non_openai_provider() -> anyhow::Result<()> { + let test = test_codex_exec(); + let server = responses::start_mock_server().await; + std::fs::write(test.home_path().join("config.toml"), "daybreak = true\n")?; + let response = responses::mount_sse_once(&server, responses::sse_completed("response")).await; + + test.cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .args([ + "--oss", + "--local-provider", + "ollama", + "--skip-git-repo-check", + "hello", + ]) + .assert() + .failure() + .stderr(predicate::str::contains( + "Daybreak requires the OpenAI model provider", + )); + assert!(response.requests().is_empty()); + Ok(()) +} + +async fn configured_daybreak_exec() -> anyhow::Result<(TestCodexExecBuilder, wiremock::MockServer)> +{ + let test = test_codex_exec(); + let server = responses::start_mock_server().await; + let token = concat!( + "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.", + "eyJodHRwczovL2FwaS5vcGVuYWkuY29tL2F1dGgiOnsiY2hhdGdwdF9wbGFuX3R5cGUiOiJlbnRlcnByaXNlIiwiY2hhdGdwdF9hY2NvdW50X2lkIjoid29ya3NwYWNlIn19.", + "c2lnbmF0dXJl", + ); + std::fs::write( + test.home_path().join("auth.json"), + serde_json::to_vec(&json!({ + "auth_mode": "chatgpt", + "tokens": {"id_token": token, "access_token": "test-token", + "refresh_token": "refresh-token", "account_id": "workspace"}, + "last_refresh": "2099-01-01T00:00:00Z" + }))?, + )?; + let catalog = test.home_path().join("catalog.json"); + std::fs::write( + &catalog, + serde_json::to_vec(&json!({"models": [{ + "slug": "gpt-test", "display_name": "Test", "base_instructions": "Test instructions", "supported_reasoning_levels": [], + "shell_type": "disabled", "visibility": "list", "supported_in_api": true, + "priority": 0, "support_verbosity": false, + "truncation_policy": {"mode": "tokens", "limit": 10000}, + "experimental_supported_tools": [], + "available_access_programs": {"cyber": ["standard", "daybreak_red", "daybreak_blue"]} + }]}))?, + )?; + std::fs::write( + test.home_path().join("config.toml"), + format!( + "cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\ndaybreak = true\n", + server.uri(), + catalog.display() + ), + )?; + wiremock::Mock::given(wiremock::matchers::method("GET")) + .and(wiremock::matchers::path("/backend-api/wham/config/bundle")) + .respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({ + "config_toml": {"enterprise_managed": []} + }))) + .mount(&server) + .await; + wiremock::Mock::given(wiremock::matchers::method("GET")) + .and(wiremock::matchers::path("/backend-api/wham/accounts/check")) + .respond_with(wiremock::ResponseTemplate::new(200).set_body_json(json!({ + "accounts": [{"id": "workspace", "workspace_backend_origin": "https://chatgpt.com", + "account_routing_override": "NO_CONSTRAINT"}] + }))) + .mount(&server) + .await; + Ok((test, server)) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resumes_and_forks_the_saved_daybreak_choice() -> anyhow::Result<()> { + let (test, server) = configured_daybreak_exec().await?; + let response = responses::mount_sse_once(&server, responses::sse_completed("started")).await; + let output = test + .cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .env_remove("CODEX_API_KEY") + .env_remove("OPENAI_API_KEY") + .args(["--skip-git-repo-check", "--json", "hello"]) + .output()?; + let thread_id = started_thread(output)?; + assert_eq!( + response.single_request().body_json()["access_programs"], + json!({"cyber": "daybreak_blue"}) + ); + + // The saved choice wins over a changed default; a CLI override affects only its invocation. + std::fs::write( + test.home_path().join("config.toml"), + format!( + "cli_auth_credentials_store = 'file'\nchatgpt_base_url = '{}/backend-api'\nmodel_catalog_json = '{}'\nmodel = 'gpt-test'\n", + server.uri(), + test.home_path().join("catalog.json").display() + ), + )?; + for (args, expected) in [ + ( + vec![ + "-c", + "daybreak=false", + "resume", + thread_id.as_str(), + "continue", + ], + "standard", + ), + ( + vec!["resume", thread_id.as_str(), "continue"], + "daybreak_blue", + ), + ( + vec!["fork", thread_id.as_str(), "continue"], + "daybreak_blue", + ), + ( + vec!["--ephemeral", "fork", thread_id.as_str(), "continue"], + "daybreak_blue", + ), + ( + vec![ + "--ephemeral", + "-c", + "daybreak=false", + "fork", + thread_id.as_str(), + "continue", + ], + "standard", + ), + ] { + let response = + responses::mount_sse_once(&server, responses::sse_completed("continued")).await; + test.cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .env_remove("CODEX_API_KEY") + .env_remove("OPENAI_API_KEY") + .arg("--skip-git-repo-check") + .args(args) + .assert() + .success(); + assert_eq!( + response.single_request().body_json()["access_programs"], + json!({"cyber": expected}) + ); + } + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_override_does_not_change_a_standard_thread() -> anyhow::Result<()> { + let (test, server) = configured_daybreak_exec().await?; + let response = + responses::mount_sse_once(&server, responses::sse_completed("started-standard")).await; + let output = test + .cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .env_remove("CODEX_API_KEY") + .env_remove("OPENAI_API_KEY") + .args([ + "--skip-git-repo-check", + "--json", + "-c", + "daybreak=false", + "hello", + ]) + .output()?; + let thread_id = started_thread(output)?; + assert_eq!( + response.single_request().body_json()["access_programs"], + json!({"cyber": "standard"}) + ); + for (args, expected) in [ + ( + vec![ + "-c", + "daybreak=true", + "resume", + thread_id.as_str(), + "continue", + ], + "daybreak_blue", + ), + (vec!["resume", thread_id.as_str(), "continue"], "standard"), + ] { + let response = + responses::mount_sse_once(&server, responses::sse_completed("continued-standard")) + .await; + test.cmd_with_server(&server) + .env_remove("CODEX_ACCESS_TOKEN") + .env_remove("CODEX_API_KEY") + .env_remove("OPENAI_API_KEY") + .arg("--skip-git-repo-check") + .args(args) + .assert() + .success(); + assert_eq!( + response.single_request().body_json()["access_programs"], + json!({"cyber": expected}) + ); + } + Ok(()) +} diff --git a/codex-rs/exec/tests/suite/mod.rs b/codex-rs/exec/tests/suite/mod.rs index 4f7a3ab89f..de152560c9 100644 --- a/codex-rs/exec/tests/suite/mod.rs +++ b/codex-rs/exec/tests/suite/mod.rs @@ -6,6 +6,7 @@ mod approval_policy; mod auth_env; #[path = "completion_backfill_tests.rs"] mod completion_backfill; +mod daybreak; mod ephemeral; mod hooks; mod mcp_required_exit; diff --git a/codex-rs/protocol/src/openai_models/access_programs.rs b/codex-rs/protocol/src/openai_models/access_programs.rs index 9a0571448d..a27c604d4b 100644 --- a/codex-rs/protocol/src/openai_models/access_programs.rs +++ b/codex-rs/protocol/src/openai_models/access_programs.rs @@ -17,6 +17,25 @@ pub struct ModelAccessPrograms { pub cyber: Vec, } +impl ModelAccessPrograms { + /// Select the preferred Daybreak treatment advertised for this model. + pub fn daybreak(&self) -> Option { + [ + CyberAccessProgram::DaybreakBlue, + CyberAccessProgram::DaybreakRed, + ] + .into_iter() + .find(|program| self.cyber.contains(program)) + } + + /// Select the standard treatment when it is advertised. + pub fn standard(&self) -> Option { + self.cyber + .contains(&CyberAccessProgram::Standard) + .then_some(CyberAccessProgram::Standard) + } +} + fn deserialize_known_cyber_access_programs<'de, D>( deserializer: D, ) -> Result, D::Error> @@ -29,3 +48,7 @@ where .filter_map(|program| serde_json::from_value(serde_json::Value::String(program)).ok()) .collect()) } + +#[cfg(test)] +#[path = "access_programs_tests.rs"] +mod tests; diff --git a/codex-rs/protocol/src/openai_models/access_programs_tests.rs b/codex-rs/protocol/src/openai_models/access_programs_tests.rs new file mode 100644 index 0000000000..176d728c0f --- /dev/null +++ b/codex-rs/protocol/src/openai_models/access_programs_tests.rs @@ -0,0 +1,24 @@ +use super::ModelAccessPrograms; +use crate::turn_input::CyberAccessProgram; +use pretty_assertions::assert_eq; + +#[test] +fn selects_only_advertised_programs_with_blue_preferred() { + use CyberAccessProgram::*; + for (cyber, expected_daybreak, expected_standard) in [ + ( + vec![Standard, DaybreakRed, DaybreakBlue], + Some(DaybreakBlue), + Some(Standard), + ), + (vec![DaybreakRed], Some(DaybreakRed), None), + (vec![Standard], None, Some(Standard)), + (vec![], None, None), + ] { + let programs = ModelAccessPrograms { cyber }; + assert_eq!( + (programs.daybreak(), programs.standard()), + (expected_daybreak, expected_standard) + ); + } +} diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 5e1d975ae8..1259a62b49 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -242,6 +242,7 @@ async fn new_config( startup_warnings: Vec::new(), bypass_hook_trust: false, model, + daybreak_enabled: false, service_tier: None, review_model: None, model_context_window: None,