1use anyhow::{Result, anyhow};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
5#[serde(rename_all = "kebab-case")]
6pub enum CodexPatchMode {
7 #[default]
9 Default,
10 ChatGptBridge,
13 ImagegenBridge,
16 #[serde(alias = "official-relay", alias = "official_relay")]
20 OfficialRelayBridge,
21 #[serde(alias = "official-imagegen", alias = "official_imagegen")]
25 OfficialImagegenBridge,
26}
27
28impl CodexPatchMode {
29 pub fn as_str(self) -> &'static str {
30 match self {
31 Self::Default => "default",
32 Self::ChatGptBridge => "chatgpt-bridge",
33 Self::ImagegenBridge => "imagegen-bridge",
34 Self::OfficialRelayBridge => "official-relay-bridge",
35 Self::OfficialImagegenBridge => "official-imagegen-bridge",
36 }
37 }
38
39 pub fn as_preset_str(self) -> &'static str {
40 match self {
41 Self::Default => "default",
42 Self::ChatGptBridge => "chatgpt-bridge",
43 Self::ImagegenBridge => "imagegen-bridge",
44 Self::OfficialRelayBridge => "official-relay",
45 Self::OfficialImagegenBridge => "official-imagegen",
46 }
47 }
48
49 pub fn is_default(self) -> bool {
50 matches!(self, Self::Default)
51 }
52
53 pub fn strips_codex_client_auth(self) -> bool {
54 matches!(
55 self,
56 Self::ChatGptBridge
57 | Self::ImagegenBridge
58 | Self::OfficialRelayBridge
59 | Self::OfficialImagegenBridge
60 )
61 }
62
63 pub fn enables_official_relay_features(self) -> bool {
64 matches!(
65 self,
66 Self::OfficialRelayBridge | Self::OfficialImagegenBridge
67 )
68 }
69
70 pub fn enables_imagegen_facade(self) -> bool {
71 matches!(self, Self::ImagegenBridge | Self::OfficialImagegenBridge)
72 }
73}
74
75impl std::fmt::Display for CodexPatchMode {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.write_str(self.as_str())
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
82pub struct CodexSwitchOptions {
83 #[serde(default, skip_serializing_if = "bool_is_false")]
87 pub responses_websocket: bool,
88}
89
90impl CodexSwitchOptions {
91 pub fn validate_for_mode(self, mode: CodexPatchMode) -> Result<()> {
92 if self.responses_websocket && !mode.enables_official_relay_features() {
93 return Err(anyhow!(
94 "Responses WebSocket transport currently requires --preset official-relay or --preset official-imagegen"
95 ));
96 }
97 Ok(())
98 }
99}
100
101fn bool_is_false(value: &bool) -> bool {
102 !*value
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum CodexPatchProviderIdentity {
107 HelperRelay,
108 OfficialOpenAi,
109}
110
111impl CodexPatchProviderIdentity {
112 pub fn provider_name(self) -> &'static str {
113 match self {
114 Self::HelperRelay => "codex-helper",
115 Self::OfficialOpenAi => "OpenAI",
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum CodexTomlBoolPatch {
122 Remove,
123 Set(bool),
124}
125
126impl CodexTomlBoolPatch {
127 pub fn value(self) -> Option<bool> {
128 match self {
129 Self::Remove => None,
130 Self::Set(value) => Some(value),
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct CodexProviderPatchPlan {
137 identity: CodexPatchProviderIdentity,
138 requires_openai_auth: CodexTomlBoolPatch,
139 supports_websockets: CodexTomlBoolPatch,
140}
141
142impl CodexProviderPatchPlan {
143 pub fn identity(self) -> CodexPatchProviderIdentity {
144 self.identity
145 }
146
147 pub fn provider_name(self) -> &'static str {
148 self.identity.provider_name()
149 }
150
151 pub fn requires_openai_auth(self) -> CodexTomlBoolPatch {
152 self.requires_openai_auth
153 }
154
155 pub fn supports_websockets(self) -> CodexTomlBoolPatch {
156 self.supports_websockets
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum CodexAuthPatchPlan {
162 RestoreOriginalIfHelperPatched,
163 PatchChatGptBridge,
164 PatchImagegenFacade,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum CodexSwitchOnEffectOrder {
169 ConfigAuthState,
170 StateConfigAuth,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct CodexPatchPlan {
175 mode: CodexPatchMode,
176 options: CodexSwitchOptions,
177 provider: CodexProviderPatchPlan,
178 auth: CodexAuthPatchPlan,
179 effect_order: CodexSwitchOnEffectOrder,
180}
181
182impl CodexPatchPlan {
183 pub fn for_switch_on(mode: CodexPatchMode, options: CodexSwitchOptions) -> Result<Self> {
184 options.validate_for_mode(mode)?;
185
186 let provider = CodexProviderPatchPlan {
187 identity: if mode.enables_official_relay_features() {
188 CodexPatchProviderIdentity::OfficialOpenAi
189 } else {
190 CodexPatchProviderIdentity::HelperRelay
191 },
192 requires_openai_auth: match mode {
193 CodexPatchMode::ChatGptBridge => CodexTomlBoolPatch::Set(true),
194 CodexPatchMode::Default
195 | CodexPatchMode::ImagegenBridge
196 | CodexPatchMode::OfficialRelayBridge
197 | CodexPatchMode::OfficialImagegenBridge => CodexTomlBoolPatch::Remove,
198 },
199 supports_websockets: match mode {
200 CodexPatchMode::Default | CodexPatchMode::ImagegenBridge => {
201 CodexTomlBoolPatch::Remove
202 }
203 CodexPatchMode::ChatGptBridge => CodexTomlBoolPatch::Set(false),
204 CodexPatchMode::OfficialRelayBridge | CodexPatchMode::OfficialImagegenBridge => {
205 CodexTomlBoolPatch::Set(options.responses_websocket)
206 }
207 },
208 };
209
210 let auth = match mode {
211 CodexPatchMode::Default | CodexPatchMode::OfficialRelayBridge => {
212 CodexAuthPatchPlan::RestoreOriginalIfHelperPatched
213 }
214 CodexPatchMode::ChatGptBridge => CodexAuthPatchPlan::PatchChatGptBridge,
215 CodexPatchMode::ImagegenBridge | CodexPatchMode::OfficialImagegenBridge => {
216 CodexAuthPatchPlan::PatchImagegenFacade
217 }
218 };
219
220 let effect_order = match auth {
221 CodexAuthPatchPlan::RestoreOriginalIfHelperPatched => {
222 CodexSwitchOnEffectOrder::ConfigAuthState
223 }
224 CodexAuthPatchPlan::PatchChatGptBridge | CodexAuthPatchPlan::PatchImagegenFacade => {
225 CodexSwitchOnEffectOrder::StateConfigAuth
226 }
227 };
228
229 Ok(Self {
230 mode,
231 options,
232 provider,
233 auth,
234 effect_order,
235 })
236 }
237
238 pub fn mode(self) -> CodexPatchMode {
239 self.mode
240 }
241
242 pub fn options(self) -> CodexSwitchOptions {
243 self.options
244 }
245
246 pub fn provider(self) -> CodexProviderPatchPlan {
247 self.provider
248 }
249
250 pub fn auth(self) -> CodexAuthPatchPlan {
251 self.auth
252 }
253
254 pub fn effect_order(self) -> CodexSwitchOnEffectOrder {
255 self.effect_order
256 }
257
258 pub fn requires_bridge_runtime_ready(self) -> bool {
259 self.mode.strips_codex_client_auth() && self.mode != CodexPatchMode::ChatGptBridge
260 }
261}