agent_profile/adapter/
codex.rs1use std::path::PathBuf;
10
11use super::{
12 Adapter, AdapterEvidence, AdapterMetadata, Capability, CapabilityClaim, CapabilityState,
13 EnvOverride, Mechanism, PathKind, PlanContext, PlannedLaunch, SupportLevel, profile_dir,
14};
15use crate::config::AppRoot;
16use crate::error::Result;
17use crate::name::ProfileName;
18
19const VAR: &str = "CODEX_HOME";
20
21pub const NEW_PROFILE_NOTE: &str =
23 "new profile starts logged out; run codex login with this profile";
24
25static METADATA: AdapterMetadata = AdapterMetadata {
26 id: "codex",
27 executable: "codex",
28 mechanism: Mechanism::Env(VAR),
29 support: SupportLevel::Proven,
30 evidence: AdapterEvidence {
31 mechanism_id: "codex-home-v1",
32 verified_at: "2026-11-09",
33 upstream_version: "0.167.0",
34 source_url: "measured",
35 notes: "codex --help: -p/--profile layers $CODEX_HOME/<name>.config.toml; binary strings OPENAI_API_KEY, \
36 CODEX_API_KEY, CODEX_ACCESS_TOKEN, CODEX_SQLITE_HOME; CODEX_HOME semantics per the Codex CLI \
37 configuration documentation",
38 },
39 capabilities: &[
40 CapabilityClaim {
41 capability: Capability::ConfigIsolation,
42 state: CapabilityState::Supported,
43 basis: "cited: config.toml and <name>.config.toml live in CODEX_HOME; project-level \
44 configuration layering is not measured",
45 },
46 CapabilityClaim {
47 capability: Capability::CredentialIsolation,
48 state: CapabilityState::Conditional,
49 basis: "measured: OPENAI_API_KEY, CODEX_API_KEY and CODEX_ACCESS_TOKEN bypass it; that \
50 auth.json and the keyring key follow CODEX_HOME is cited, not measured",
51 },
52 CapabilityClaim {
53 capability: Capability::StateIsolation,
54 state: CapabilityState::Conditional,
55 basis: "cited: CODEX_SQLITE_HOME relocates the state database; the variable is present in \
56 the binary but the probe sets only CODEX_HOME, so the relocation is not measured",
57 },
58 ],
59 env: &[EnvOverride { name: VAR, sensitive: false }],
60 conflicts: &[],
61 credential_env: &["OPENAI_API_KEY", "CODEX_API_KEY", "CODEX_ACCESS_TOKEN"],
62};
63
64#[derive(Debug, Clone, Copy)]
66pub struct Codex;
67
68impl Adapter for Codex {
69 fn metadata(&self) -> &'static AdapterMetadata {
70 &METADATA
71 }
72
73 fn paths(&self, root: &AppRoot, profile: &ProfileName) -> Vec<(PathBuf, PathKind)> {
74 vec![(profile_dir(root, profile, METADATA.id), PathKind::Dir)]
75 }
76
77 fn plan(&self, ctx: &PlanContext<'_>) -> Result<PlannedLaunch> {
78 let mut planned = METADATA.mechanism.plan(self, ctx)?;
79 if !planned.paths[0].existed {
80 planned.notes.push(NEW_PROFILE_NOTE.to_owned());
81 }
82 Ok(planned)
83 }
84}