diff --git a/epiphany-openai-codex-spine/Cargo.toml b/epiphany-openai-codex-spine/Cargo.toml index e9eddda1..1025729b 100644 --- a/epiphany-openai-codex-spine/Cargo.toml +++ b/epiphany-openai-codex-spine/Cargo.toml @@ -20,7 +20,3 @@ serde = "1" serde_json = "1" sha2 = "0.10" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } - -[[bin]] -name = "epiphany-openai-spine" -path = "src/bin/epiphany-openai-spine.rs" diff --git a/epiphany-openai-codex-spine/src/bin/epiphany-openai-spine.rs b/epiphany-openai-codex-spine/src/bin/epiphany-openai-spine.rs deleted file mode 100644 index 4e08b6df..00000000 --- a/epiphany-openai-codex-spine/src/bin/epiphany-openai-spine.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::env; -use std::fs; -use std::path::PathBuf; - -use anyhow::Context; -use anyhow::Result; -use anyhow::anyhow; -use epiphany_openai_adapter::EpiphanyOpenAiInputItem; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; -use epiphany_openai_auth_spine::CodexAuth; -use epiphany_openai_codex_spine::EpiphanyCodexOpenAiTransport; -use epiphany_openai_codex_spine::auth_manager; -use epiphany_openai_codex_spine::default_codex_home; -use epiphany_openai_codex_spine::responses_body_from_epiphany; -use epiphany_openai_codex_spine::status_from_auth_manager; -use epiphany_openai_codex_spine::status_from_codex_auth; -use serde_json::json; - -#[tokio::main] -async fn main() -> Result<()> { - let mut args = env::args().skip(1); - let command = args.next().unwrap_or_else(|| "usage".to_string()); - match command.as_str() { - "status" => { - let options = parse_common_options(args.collect())?; - let auth_manager = auth_manager(options.codex_home); - let status = status_from_auth_manager( - &auth_manager, - options.default_model, - /*supports_websockets*/ true, - ) - .await; - print_json(&status)?; - } - "model-turn" => { - let options = parse_model_turn_options(args.collect())?; - let request_text = fs::read_to_string(&options.request_path) - .with_context(|| format!("failed to read {}", options.request_path.display()))?; - let request: EpiphanyOpenAiModelRequest = serde_json::from_str(&request_text) - .with_context(|| format!("failed to parse {}", options.request_path.display()))?; - let transport = EpiphanyCodexOpenAiTransport::openai(auth_manager(options.codex_home)); - let events = transport.collect_model_events(request).await?; - print_json(&events)?; - } - "smoke" => { - let auth = CodexAuth::from_api_key("test-key"); - let status = status_from_codex_auth(Some(&auth), Some("gpt-5.4".to_string()), true); - let mut request = EpiphanyOpenAiModelRequest::new( - "smoke-request", - "smoke-conversation", - "gpt-5.4", - "Answer with one sentence.", - ); - request.input.push(EpiphanyOpenAiInputItem::UserText { - text: "smoke".to_string(), - }); - let mapped = responses_body_from_epiphany(request)?; - print_json(&json!({ - "status": status, - "mappedModel": mapped.get("model"), - "mappedInputItems": mapped.get("input").and_then(serde_json::Value::as_array).map(Vec::len), - "transport": "not-opened" - }))?; - } - _ => { - return Err(anyhow!( - "usage: epiphany-openai-spine [--codex-home path] [--default-model model] [--request path]" - )); - } - } - Ok(()) -} - -struct CommonOptions { - codex_home: PathBuf, - default_model: Option, -} - -struct ModelTurnOptions { - codex_home: PathBuf, - request_path: PathBuf, -} - -fn parse_common_options(args: Vec) -> Result { - let mut codex_home = default_codex_home()?; - let mut default_model = None; - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--codex-home" => { - codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?); - } - "--default-model" => { - default_model = Some(next_value(&mut iter, "--default-model")?); - } - other => return Err(anyhow!("unknown status argument: {other}")), - } - } - Ok(CommonOptions { - codex_home, - default_model, - }) -} - -fn parse_model_turn_options(args: Vec) -> Result { - let mut codex_home = default_codex_home()?; - let mut request_path = None; - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--codex-home" => { - codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?); - } - "--request" => { - request_path = Some(PathBuf::from(next_value(&mut iter, "--request")?)); - } - other => return Err(anyhow!("unknown model-turn argument: {other}")), - } - } - Ok(ModelTurnOptions { - codex_home, - request_path: request_path.context("model-turn requires --request")?, - }) -} - -fn next_value(iter: &mut impl Iterator, name: &str) -> Result { - iter.next() - .ok_or_else(|| anyhow!("{name} requires a value")) -} - -fn print_json(value: &T) -> Result<()> { - println!("{}", serde_json::to_string_pretty(value)?); - Ok(()) -} diff --git a/epiphany-openai-codex-spine/src/lib.rs b/epiphany-openai-codex-spine/src/lib.rs index 0a7bdf6a..c56b5bc2 100644 --- a/epiphany-openai-codex-spine/src/lib.rs +++ b/epiphany-openai-codex-spine/src/lib.rs @@ -85,25 +85,6 @@ pub async fn status_from_auth_manager( } } -pub fn status_from_codex_auth( - auth: Option<&CodexAuth>, - default_model: Option, - supports_websockets: bool, -) -> EpiphanyOpenAiAdapterStatus { - EpiphanyOpenAiAdapterStatus { - schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(), - 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, - } -} - pub fn status_from_static_api_key( adapter_id: &str, default_model: Option, @@ -1523,27 +1504,6 @@ mod tests { request } - #[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); - } - #[test] fn static_openrouter_status_does_not_claim_codex_transport() { let status = status_from_static_api_key( diff --git a/notes/codex-auth-spine-inventory.md b/notes/codex-auth-spine-inventory.md index 9048773b..255c60dc 100644 --- a/notes/codex-auth-spine-inventory.md +++ b/notes/codex-auth-spine-inventory.md @@ -163,15 +163,8 @@ contracts in its hello/schema catalog. This is the contract bridge the native runtime should consume next. Do not add another JSON-RPC model endpoint and call it progress. -The first native operator/debug edge now exists as the -`epiphany-openai-spine` binary in `epiphany-openai-codex-spine`. It can report -typed adapter status and consume a serialized `EpiphanyOpenAiModelRequest` -document for a model turn without going through Codex app-server JSON-RPC. This -is not the final CultNet daemon. It is a buildable extracted edge that proves -the spine can be called from Epiphany-owned code and gives the next cut a place -to route through while the old `thread/epiphany/*` model path is starved. - -The native runtime route now exists too. `epiphany-openai-adapter` documents +The native runtime route is the operator and daemon edge. +`epiphany-openai-adapter` documents derive CultCache `DatabaseEntry`, `epiphany-core::runtime_spine_cache` registers OpenAI adapter status/request/stream-event/receipt documents, and the outside-vendor `epiphany-openai-runtime` crate records typed OpenAI model-turn