Add Codex OpenAI auth spine wrapper

This commit is contained in:
Meta Vens 2026-05-13 04:50:21 +01:00
commit 7bc3a5cb63
11 changed files with 697 additions and 99 deletions

2
.gitignore vendored
View file

@ -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/

View file

@ -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"

View file

@ -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;

View file

@ -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)]

View file

@ -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" }

View file

@ -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<String>,
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<String>,
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);
}
}

View file

@ -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.

View file

@ -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

Binary file not shown.

View file

@ -279,5 +279,5 @@ rejected_paths:
reason: making Epiphany cleaner inside Codex still preserves the wrong organ; Codex must be starved down to OpenAI subscription auth/model transport, while Epiphany data and subsystem communication move to typed CultCache/CultNet.
current_status:
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, and now the first native `epiphany-openai-adapter` crate boundary. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,071 lines, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` currently defines typed request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency; a direct standalone `codex-core` wrapper attempt hit dependency skew outside the vendored workspace lock and is recorded as a constraint, not papered over. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON.
next_action: Continue Codex starvation by wiring `epiphany-openai-adapter` to the keeper Codex auth/model transport under a workspace-verified dependency regime, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion.
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, the pure native `epiphany-openai-adapter` document boundary, and the first workspace-verified Codex auth wrapper in `epiphany-openai-codex-spine`. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,071 lines, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` defines typed request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency. `epiphany-openai-codex-spine` depends on the keeper Codex auth types and projects `AuthManager` / `CodexAuth` into typed `EpiphanyOpenAiAdapterStatus`; it compiles through the vendored Codex app-server graph via `epiphany-codex-bridge` without contaminating the pure adapter crate. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON.
next_action: Continue Codex starvation by adding model transport behind the typed `epiphany-openai-adapter` request/event/receipt surface, using the keeper Codex Responses API transport under the workspace-verified spine boundary, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion.

609
vendor/codex/codex-rs/Cargo.lock generated vendored

File diff suppressed because it is too large Load diff