Files
codex/codex-rs/tui/src/daybreak.rs
T
Brian Lee 6cc7d9cc5c Enable Cyber access programs for API-key sessions by default (#53717)
## What changed

- Default `features.api_key_cyber_access_programs` to `true` and allow `codex exec` to select Daybreak with an OpenAI API key. Select the advertised standard program when `daybreak=false`, while respecting explicit feature opt-outs.
- Resolve API-key eligibility for TUI background turns from the target thread's feature settings, including project opt-outs. Omit access-program overrides when the server cannot report those settings.
- Preserve automatic server treatment for unlisted models and missing or empty Cyber program metadata in exec and TUI. Continue rejecting Daybreak when a nonempty program list does not offer it.

## Testing

Update regression coverage for API-key Daybreak selection, the off override, explicit opt-outs, resumed sessions, target-thread settings, and missing model metadata.

GitOrigin-RevId: f3cf7bd95a2754d4b4281bc70b18c7da4b177552
2026-10-13 16:57:54 +00:00

109 lines
3.2 KiB
Rust

//! Account-scoped Daybreak eligibility and per-turn program selection.
//! Pending or failed discovery never implies access.
use codex_protocol::openai_models::ModelAccessPrograms;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::turn_input::CyberAccessProgram;
pub(crate) fn program_for_turn(
models: &[ModelPreset],
model: &str,
eligible_account: bool,
enabled: bool,
) -> Result<Option<CyberAccessProgram>, String> {
if !eligible_account {
return if enabled {
Err("Daybreak requires the OpenAI provider with either ChatGPT sign-in or an API key with Daybreak support enabled. Turn it off to continue.".into())
} else {
Ok(None)
};
}
let programs = models
.iter()
.find(|entry| entry.model == model)
.and_then(|entry| entry.available_access_programs.as_ref())
.filter(|programs| !programs.cyber.is_empty());
let Some(programs) = programs else {
// Missing discovery metadata preserves the server's automatic treatment.
return Ok(None);
};
if enabled {
programs
.daybreak()
.map(Some)
.ok_or_else(|| format!("Daybreak support for model {model} could not be confirmed by the connected server. Use /daybreak to turn it off, or choose a compatible model and server."))
} else {
Ok(programs.standard())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Notice {
Apply,
Astra,
Disabled,
Enabled,
}
pub(crate) fn notice_for_setting(
models: &[ModelPreset],
model: &str,
enabled: bool,
can_enable_daybreak: bool,
) -> Notice {
if enabled {
return Notice::Enabled;
}
if can_enable_daybreak {
let programs = models
.iter()
.find(|entry| entry.model == model)
.and_then(|entry| entry.available_access_programs.as_ref());
if matches!(model, "gpt-6-astra" | "gpt-6-astra-wm")
&& programs.is_some_and(|programs| {
programs.cyber.contains(&CyberAccessProgram::Standard)
&& programs.daybreak().is_none()
})
{
return Notice::Astra;
}
if programs.and_then(ModelAccessPrograms::daybreak).is_some() {
return Notice::Disabled;
}
}
Notice::Apply
}
pub(crate) fn available(models: &[ModelPreset]) -> bool {
models.iter().any(|model| {
model
.available_access_programs
.as_ref()
.and_then(codex_protocol::openai_models::ModelAccessPrograms::daybreak)
.is_some()
})
}
/// Missing or empty program lists do not establish that the account lacks access.
pub(crate) fn availability(models: &[ModelPreset]) -> Option<bool> {
if available(models) {
return Some(true);
}
if models.is_empty() {
return None;
}
models
.iter()
.all(|model| {
model
.available_access_programs
.as_ref()
.is_some_and(|programs| !programs.cyber.is_empty())
})
.then_some(false)
}
#[cfg(test)]
#[path = "daybreak_tests.rs"]
mod tests;