From 7bc3a5cb63a8fc3349577ddf3201f1b1383d983b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 13 May 2026 04:50:21 +0100 Subject: [PATCH] Add Codex OpenAI auth spine wrapper --- .gitignore | 2 + epiphany-codex-bridge/Cargo.toml | 2 + epiphany-codex-bridge/src/lib.rs | 5 +- epiphany-openai-adapter/src/lib.rs | 22 +- epiphany-openai-codex-spine/Cargo.toml | 15 + epiphany-openai-codex-spine/src/lib.rs | 102 +++++ notes/codex-auth-spine-inventory.md | 15 +- notes/fresh-workspace-handoff.md | 20 +- state/ledgers.msgpack | Bin 80691 -> 81021 bytes state/map.yaml | 4 +- vendor/codex/codex-rs/Cargo.lock | 609 +++++++++++++++++++++---- 11 files changed, 697 insertions(+), 99 deletions(-) create mode 100644 epiphany-openai-codex-spine/Cargo.toml create mode 100644 epiphany-openai-codex-spine/src/lib.rs diff --git a/.gitignore b/.gitignore index a641fe9a..2565ca6f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ epiphany-core/target/ epiphany-core/Cargo.lock epiphany-openai-adapter/target/ epiphany-openai-adapter/Cargo.lock +epiphany-openai-codex-spine/target/ +epiphany-openai-codex-spine/Cargo.lock apps/epiphany-gui/node_modules/ apps/epiphany-gui/dist/ apps/epiphany-gui/src-tauri/gen/ diff --git a/epiphany-codex-bridge/Cargo.toml b/epiphany-codex-bridge/Cargo.toml index 5cc369f7..01316b04 100644 --- a/epiphany-codex-bridge/Cargo.toml +++ b/epiphany-codex-bridge/Cargo.toml @@ -16,6 +16,8 @@ codex-core = { path = "../vendor/codex/codex-rs/core" } codex-protocol = { path = "../vendor/codex/codex-rs/protocol" } codex-utils-absolute-path = { path = "../vendor/codex/codex-rs/utils/absolute-path" } epiphany-core = { path = "../epiphany-core" } +epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } +epiphany-openai-codex-spine = { path = "../epiphany-openai-codex-spine" } serde_json = "1" serde = { version = "1", features = ["derive"] } toml = "0.9" diff --git a/epiphany-codex-bridge/src/lib.rs b/epiphany-codex-bridge/src/lib.rs index 2e0a89de..100e22e5 100644 --- a/epiphany-codex-bridge/src/lib.rs +++ b/epiphany-codex-bridge/src/lib.rs @@ -5,8 +5,11 @@ pub mod launch; pub mod mutation; pub mod pressure; pub mod reorient; -pub mod retrieve; pub mod results; +pub mod retrieve; pub mod runtime_results; pub mod scene; pub mod state; + +pub use epiphany_openai_adapter as openai_adapter; +pub use epiphany_openai_codex_spine as openai_codex_spine; diff --git a/epiphany-openai-adapter/src/lib.rs b/epiphany-openai-adapter/src/lib.rs index 4f2e76bf..cd32b457 100644 --- a/epiphany-openai-adapter/src/lib.rs +++ b/epiphany-openai-adapter/src/lib.rs @@ -77,11 +77,23 @@ pub struct EpiphanyOpenAiStreamEvent { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum EpiphanyOpenAiStreamPayload { - TextDelta { text: String }, - ReasoningDelta { text: String }, - ToolCall { call_id: String, name: String, arguments: String }, - Completed { receipt: EpiphanyOpenAiModelReceipt }, - Failed { message: String }, + TextDelta { + text: String, + }, + ReasoningDelta { + text: String, + }, + ToolCall { + call_id: String, + name: String, + arguments: String, + }, + Completed { + receipt: EpiphanyOpenAiModelReceipt, + }, + Failed { + message: String, + }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/epiphany-openai-codex-spine/Cargo.toml b/epiphany-openai-codex-spine/Cargo.toml new file mode 100644 index 00000000..7c11fcc6 --- /dev/null +++ b/epiphany-openai-codex-spine/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "epiphany-openai-codex-spine" +version = "0.1.0" +edition = "2024" +publish = false +license-file = "../LICENSE" + +[lib] +name = "epiphany_openai_codex_spine" +path = "src/lib.rs" + +[dependencies] +codex-app-server-protocol = { path = "../vendor/codex/codex-rs/app-server-protocol" } +codex-login = { path = "../vendor/codex/codex-rs/login" } +epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } diff --git a/epiphany-openai-codex-spine/src/lib.rs b/epiphany-openai-codex-spine/src/lib.rs new file mode 100644 index 00000000..94b2626b --- /dev/null +++ b/epiphany-openai-codex-spine/src/lib.rs @@ -0,0 +1,102 @@ +use codex_app_server_protocol::AuthMode; +use codex_login::AuthManager; +use codex_login::CodexAuth; +use epiphany_openai_adapter::EpiphanyOpenAiAdapterStatus; +use epiphany_openai_adapter::EpiphanyOpenAiAuthMode; + +pub const CODEX_SPINE_ADAPTER_ID: &str = "codex-openai-subscription-spine"; + +pub async fn status_from_auth_manager( + auth_manager: &AuthManager, + default_model: Option, + supports_websockets: bool, +) -> EpiphanyOpenAiAdapterStatus { + let auth = auth_manager.auth().await; + let auth_mode = auth_mode_from_manager(auth_manager, auth.as_ref()); + let account_id = auth.as_ref().and_then(CodexAuth::get_account_id); + let plan_type = auth + .as_ref() + .and_then(CodexAuth::account_plan_type) + .map(|plan| format!("{plan:?}")); + + EpiphanyOpenAiAdapterStatus { + adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(), + auth_mode, + account_id, + plan_type, + default_model, + supports_websockets, + codex_transport_attached: true, + } +} + +pub fn status_from_codex_auth( + auth: Option<&CodexAuth>, + default_model: Option, + supports_websockets: bool, +) -> EpiphanyOpenAiAdapterStatus { + EpiphanyOpenAiAdapterStatus { + adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(), + auth_mode: auth_mode_from_codex_auth(auth), + account_id: auth.and_then(CodexAuth::get_account_id), + plan_type: auth + .and_then(CodexAuth::account_plan_type) + .map(|plan| format!("{plan:?}")), + default_model, + supports_websockets, + codex_transport_attached: true, + } +} + +fn auth_mode_from_manager( + auth_manager: &AuthManager, + auth: Option<&CodexAuth>, +) -> EpiphanyOpenAiAuthMode { + match auth_manager.auth_mode() { + Some(AuthMode::ApiKey) => EpiphanyOpenAiAuthMode::ApiKey, + Some(AuthMode::Chatgpt) | Some(AuthMode::ChatgptAuthTokens) => { + EpiphanyOpenAiAuthMode::ChatGptSubscription + } + Some(AuthMode::AgentIdentity) => EpiphanyOpenAiAuthMode::ExternalBearer, + None => auth_mode_from_codex_auth(auth), + } +} + +fn auth_mode_from_codex_auth(auth: Option<&CodexAuth>) -> EpiphanyOpenAiAuthMode { + let Some(auth) = auth else { + return EpiphanyOpenAiAuthMode::Unknown; + }; + if auth.is_api_key_auth() { + EpiphanyOpenAiAuthMode::ApiKey + } else if auth.is_chatgpt_auth() { + EpiphanyOpenAiAuthMode::ChatGptSubscription + } else { + EpiphanyOpenAiAuthMode::ExternalBearer + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn api_key_auth_maps_to_typed_adapter_status() { + let auth = CodexAuth::from_api_key("test-key"); + let status = status_from_codex_auth(Some(&auth), Some("gpt-5.4".to_string()), true); + + assert_eq!(status.adapter_id, CODEX_SPINE_ADAPTER_ID); + assert_eq!(status.auth_mode, EpiphanyOpenAiAuthMode::ApiKey); + assert_eq!(status.default_model.as_deref(), Some("gpt-5.4")); + assert!(status.supports_websockets); + assert!(status.codex_transport_attached); + } + + #[test] + fn missing_auth_maps_to_unknown_status() { + let status = status_from_codex_auth(None, None, false); + + assert_eq!(status.auth_mode, EpiphanyOpenAiAuthMode::Unknown); + assert_eq!(status.account_id, None); + assert!(!status.supports_websockets); + } +} diff --git a/notes/codex-auth-spine-inventory.md b/notes/codex-auth-spine-inventory.md index f884eacb..dd6f9c92 100644 --- a/notes/codex-auth-spine-inventory.md +++ b/notes/codex-auth-spine-inventory.md @@ -115,10 +115,17 @@ surface: The first attempt to wire `codex-core` directly as a standalone path dependency escaped the vendored workspace lock and hit transitive ICU/temporal dependency -skew. Do not paper over that with random version pins. The next implementation -slice should add a Codex-transport feature or workspace-verified wrapper that -builds under the same dependency regime as vendored Codex, while preserving the -typed request/event/receipt surface as the Epiphany-owned contract. +skew. Do not paper over that with random version pins. + +The first workspace-verified wrapper now exists as +`epiphany-openai-codex-spine`. It depends on the pure typed adapter plus the +keeper Codex auth types and projects `AuthManager` / `CodexAuth` into a typed +`EpiphanyOpenAiAdapterStatus`. `epiphany-codex-bridge` re-exports this spine so +the current app-server compatibility shell can compile it without making the +pure document crate depend on Codex. The remaining extraction is model +transport: typed Epiphany model requests must call the keeper Responses API +transport through this boundary rather than route through Epiphany JSON-RPC or +the Codex host brain. The point is ownership: Epiphany calls a model adapter; it does not live inside the Codex host brain. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index e1828162..83bf9118 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -550,20 +550,22 @@ compiles as a native typed boundary: auth status, model request, stream event, and terminal receipt documents. It intentionally has no Codex app-server dependency and no generic JSON payload. A direct standalone `codex-core` path dependency attempt hit transitive dependency skew outside the vendored Codex -workspace lock; do not "fix" that with random version pins. The next slice is a -workspace-verified Codex transport feature/wrapper behind the typed adapter -surface. +workspace lock; do not "fix" that with random version pins. The first +workspace-verified wrapper now exists as `epiphany-openai-codex-spine`: it +depends on the pure typed adapter plus the keeper Codex auth types and projects +`AuthManager` / `CodexAuth` into a typed `EpiphanyOpenAiAdapterStatus`. +`epiphany-codex-bridge` re-exports that spine so the current app-server shell +can compile the attachment without contaminating the pure document crate. Also: MCP itself is allowed to be JSON. The target is not "replace MCP JSON"; the target is an Epiphany-owned boundary that speaks typed Epiphany intent/result/receipt documents internally and normal MCP JSON-RPC externally. -Continue with the actual whale-carcass cut: create the first -`epiphany-openai-adapter` Codex transport integration behind the native typed -request/event/receipt surface, using the keeper auth/model transport spine -identified in `notes/codex-auth-spine-inventory.md`. Success is Epiphany -calling a model adapter rather than living inside the Codex host brain. Do not -resume Rider, Unity, Aquarium, Face, dogfood, planning, app, skill, +Continue with the actual whale-carcass cut: add model transport behind the +native typed request/event/receipt surface, using the keeper Responses API +transport identified in `notes/codex-auth-spine-inventory.md`. Success is +Epiphany calling a model adapter rather than living inside the Codex host brain. +Do not resume Rider, Unity, Aquarium, Face, dogfood, planning, app, skill, marketplace, or bridge expansion until this organ is being cut cleanly. The Phase 6 freshness slice is landed. It exposes read-only diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index df358e47023a842a544c5af72295016a9ad67c54..b3ff3be96c55181ba9f990824665aeb13d03874f 100644 GIT binary patch delta 331 zcmZ8dJxT*X6lNr7?FoD!sbqBBn1In{36^4EK(Kr6yzGo7GmqI>W2y+bfFS5OM8PJv z@GfE_IN42Q`|rT2H$hr-Qflix6LnrTAP5fyb|lw%Fe4^pAS|Zu*>E?aenc zrAPr+o`Be}Mmv*^$)b^{kOO%LGmaGH@@hghl)&SvgsJm)C1XzVp1c{E$oQ|%ouUU> zvatkM1ue^fVI}p*O#TE=&6@;I6EwDjL9G~hBA9A3CJ36KF<|d*DUazj2eRmQjus2@ z2Q{(+z}Vo{b