use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum CodexPatchMode {
#[default]
Default,
ChatGptBridge,
ImagegenBridge,
#[serde(alias = "official-relay", alias = "official_relay")]
OfficialRelayBridge,
#[serde(alias = "official-imagegen", alias = "official_imagegen")]
OfficialImagegenBridge,
}
impl CodexPatchMode {
pub fn as_str(self) -> &'static str {
match self {
Self::Default => "default",
Self::ChatGptBridge => "chatgpt-bridge",
Self::ImagegenBridge => "imagegen-bridge",
Self::OfficialRelayBridge => "official-relay-bridge",
Self::OfficialImagegenBridge => "official-imagegen-bridge",
}
}
pub fn as_preset_str(self) -> &'static str {
match self {
Self::Default => "default",
Self::ChatGptBridge => "chatgpt-bridge",
Self::ImagegenBridge => "imagegen-bridge",
Self::OfficialRelayBridge => "official-relay",
Self::OfficialImagegenBridge => "official-imagegen",
}
}
pub fn is_default(self) -> bool {
matches!(self, Self::Default)
}
pub fn strips_codex_client_auth(self) -> bool {
matches!(
self,
Self::ChatGptBridge
| Self::ImagegenBridge
| Self::OfficialRelayBridge
| Self::OfficialImagegenBridge
)
}
pub fn enables_official_relay_features(self) -> bool {
matches!(
self,
Self::OfficialRelayBridge | Self::OfficialImagegenBridge
)
}
pub fn enables_imagegen_facade(self) -> bool {
matches!(self, Self::ImagegenBridge | Self::OfficialImagegenBridge)
}
}
impl std::fmt::Display for CodexPatchMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
pub struct CodexSwitchOptions {
#[serde(default, skip_serializing_if = "bool_is_false")]
pub responses_websocket: bool,
}
impl CodexSwitchOptions {
pub fn validate_for_mode(self, mode: CodexPatchMode) -> Result<()> {
if self.responses_websocket && !mode.enables_official_relay_features() {
return Err(anyhow!(
"Responses WebSocket transport currently requires --preset official-relay or --preset official-imagegen"
));
}
Ok(())
}
}
fn bool_is_false(value: &bool) -> bool {
!*value
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodexPatchProviderIdentity {
HelperRelay,
OfficialOpenAi,
}
impl CodexPatchProviderIdentity {
pub fn provider_name(self) -> &'static str {
match self {
Self::HelperRelay => "codex-helper",
Self::OfficialOpenAi => "OpenAI",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodexTomlBoolPatch {
Remove,
Set(bool),
}
impl CodexTomlBoolPatch {
pub fn value(self) -> Option<bool> {
match self {
Self::Remove => None,
Self::Set(value) => Some(value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodexProviderPatchPlan {
identity: CodexPatchProviderIdentity,
requires_openai_auth: CodexTomlBoolPatch,
supports_websockets: CodexTomlBoolPatch,
}
impl CodexProviderPatchPlan {
pub fn identity(self) -> CodexPatchProviderIdentity {
self.identity
}
pub fn provider_name(self) -> &'static str {
self.identity.provider_name()
}
pub fn requires_openai_auth(self) -> CodexTomlBoolPatch {
self.requires_openai_auth
}
pub fn supports_websockets(self) -> CodexTomlBoolPatch {
self.supports_websockets
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodexAuthPatchPlan {
RestoreOriginalIfHelperPatched,
PatchChatGptBridge,
PatchImagegenFacade,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodexSwitchOnEffectOrder {
ConfigAuthState,
StateConfigAuth,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodexPatchPlan {
mode: CodexPatchMode,
options: CodexSwitchOptions,
provider: CodexProviderPatchPlan,
auth: CodexAuthPatchPlan,
effect_order: CodexSwitchOnEffectOrder,
}
impl CodexPatchPlan {
pub fn for_switch_on(mode: CodexPatchMode, options: CodexSwitchOptions) -> Result<Self> {
options.validate_for_mode(mode)?;
let provider = CodexProviderPatchPlan {
identity: if mode.enables_official_relay_features() {
CodexPatchProviderIdentity::OfficialOpenAi
} else {
CodexPatchProviderIdentity::HelperRelay
},
requires_openai_auth: match mode {
CodexPatchMode::ChatGptBridge => CodexTomlBoolPatch::Set(true),
CodexPatchMode::Default
| CodexPatchMode::ImagegenBridge
| CodexPatchMode::OfficialRelayBridge
| CodexPatchMode::OfficialImagegenBridge => CodexTomlBoolPatch::Remove,
},
supports_websockets: match mode {
CodexPatchMode::Default | CodexPatchMode::ImagegenBridge => {
CodexTomlBoolPatch::Remove
}
CodexPatchMode::ChatGptBridge => CodexTomlBoolPatch::Set(false),
CodexPatchMode::OfficialRelayBridge | CodexPatchMode::OfficialImagegenBridge => {
CodexTomlBoolPatch::Set(options.responses_websocket)
}
},
};
let auth = match mode {
CodexPatchMode::Default | CodexPatchMode::OfficialRelayBridge => {
CodexAuthPatchPlan::RestoreOriginalIfHelperPatched
}
CodexPatchMode::ChatGptBridge => CodexAuthPatchPlan::PatchChatGptBridge,
CodexPatchMode::ImagegenBridge | CodexPatchMode::OfficialImagegenBridge => {
CodexAuthPatchPlan::PatchImagegenFacade
}
};
let effect_order = match auth {
CodexAuthPatchPlan::RestoreOriginalIfHelperPatched => {
CodexSwitchOnEffectOrder::ConfigAuthState
}
CodexAuthPatchPlan::PatchChatGptBridge | CodexAuthPatchPlan::PatchImagegenFacade => {
CodexSwitchOnEffectOrder::StateConfigAuth
}
};
Ok(Self {
mode,
options,
provider,
auth,
effect_order,
})
}
pub fn mode(self) -> CodexPatchMode {
self.mode
}
pub fn options(self) -> CodexSwitchOptions {
self.options
}
pub fn provider(self) -> CodexProviderPatchPlan {
self.provider
}
pub fn auth(self) -> CodexAuthPatchPlan {
self.auth
}
pub fn effect_order(self) -> CodexSwitchOnEffectOrder {
self.effect_order
}
pub fn requires_bridge_runtime_ready(self) -> bool {
self.mode.strips_codex_client_auth() && self.mode != CodexPatchMode::ChatGptBridge
}
}