Codex 系统手册

身份验证、身份和账号就绪

stage-540 个文件

这一阶段像系统开工前的“验票和办证”。启动时先读出以前保存的登录状态;没有票或票过期,就引导用户登录、刷新,或退出时撤销旧票。登录总管把 API Key、ChatGPT 令牌、个人访问令牌、Agent 身份、Bedrock Key 统一管起来。令牌解析器看懂这串票是谁的、什么套餐、何时过期。提供商适配器再把不同服务需要的钥匙、签名和请求头装好。安装编号和 Windows 沙箱用户则帮系统认准本机和安全身份。

子阶段

本阶段涉及的状态6
  • reg-installation-environment程序知道自己装在哪里、家目录在哪里、运行在哪台机器和什么终端里的那组环境信息。
  • reg-auth-account用户当前是谁、有没有登录、用的是哪种账号或套餐、令牌是否还能用的身份状态。
  • reg-secret-store本机保存的 API Key、ChatGPT 令牌、个人访问令牌、Bedrock Key 等秘密凭据。
  • reg-account-quota-limits账号的额度、限流、消费控制和剩余可用量信息,用来决定请求能不能继续发。
  • reg-attestation-state宿主或客户端提供的证明材料、证明请求结果以及附加到上游请求的 attestation 头状态。
  • reg-installation-identity本机安装实例的持久编号和识别信息,用于账号、后端请求、遥测和安全身份关联。

本阶段的文件6

认证状态核心

这些文件定义主认证状态机,以及它在启动期间加载、分类、解析和补充的令牌表示。

login/src/auth/manager.rs源码 ↗
domain_logicstartup, request handling, auth refresh, logout, cross-cutting

这个文件解决的是“程序到底该拿哪张通行证去访问服务”的问题。用户可能把凭证存在文件里、钥匙串里、内存里,也可能通过环境变量或外部程序提供;如果没有一个统一入口,程序各处会读到不一样的登录状态,出错时也不知道该刷新还是让用户重登。这里的核心是 AuthManager,它像前台保管员:启动时读一次凭证,缓存起来;别人要用时发一份副本;需要时主动刷新 ChatGPT 令牌;遇到 401 未授权时,按步骤尝试重读、刷新或请求外部凭证。CodexAuth 则是“各种通行证的统一包装”。文件还包含登录、登出、保存、加载、撤销令牌、限制登录方式和工作区等操作,确保凭证既能用,又不会串账号或越过管理员限制。

函数细节113
CodexAuth::eq67–73 ↗
fn eq(&self, other: &Self) -> bool

作用:判断两份登录身份能不能算作同一个。对普通模式主要比登录类型,对个人令牌和 Bedrock Key 会比实际内容。

数据流:输入两份 CodexAuth → 按类型比较,必要时读取它们的 API 认证模式 → 输出 true 或 false,不改动任何状态。

调用关系:它支撑缓存更新时的“有没有变”判断;内部会用 CodexAuth::api_auth_mode 来做大类比较。

调用图:调用 1 个内部函数(api_auth_mode);外部调用 1 个(api_auth_mode)。

ExternalAuthTokens::access_token_only136–141 ↗
fn access_token_only(access_token: impl Into<String>) -> Self

作用:造一份只有访问令牌、没有 ChatGPT 账号信息的外部凭证。适合外部 API Key 这类不需要账号元数据的场景。

数据流:输入一个令牌字符串 → 转成内部保存的 access_token,并把 ChatGPT 元数据设为空 → 返回 ExternalAuthTokens。

调用关系:外部认证提供者的 resolve 或 refresh 可以用它返回简单凭证;如果拿它去创建 ChatGPT 外部登录,会因为缺少元数据而失败。

调用图:被 5 处调用(external_auth_tokens_without_chatgpt_metadata_cannot_seed_chatgpt_auth, refresh, resolve, refresh, resolve);外部调用 1 个(into)。

ExternalAuthTokens::chatgpt143–155 ↗
fn chatgpt(
        access_token: impl Into<String>,
        chatgpt_account_id: impl Into<String>,
        chatgpt_plan_type: Option<String>,
    ) -> Self

作用:造一份带 ChatGPT 账号信息的外部凭证。外部应用刷新 ChatGPT 登录后,会用它把令牌和账号 ID 一起交回来。

数据流:输入访问令牌、ChatGPT 账号 ID、可选套餐类型 → 打包成 ExternalAuthTokens → 返回给调用方。

调用关系:AuthDotJson::from_external_access_token 会调用它;外部认证的 refresh 也会用它把新令牌交给 AuthManager。

调用图:被 2 处调用(refresh, from_external_access_token);外部调用 1 个(into)。

ExternalAuthTokens::chatgpt_metadata157–159 ↗
fn chatgpt_metadata(&self) -> Option<&ExternalAuthChatgptMetadata>

作用:取出外部凭证里的 ChatGPT 账号附加信息。没有这些信息时,系统不能安全地当作 ChatGPT 登录使用。

数据流:输入一份 ExternalAuthTokens → 查看里面有没有 chatgpt_metadata → 返回引用或 None,不修改数据。

调用关系:AuthDotJson::from_external_tokens 会用它确认外部令牌属于哪个 ChatGPT 账号。

调用图:被 1 处调用(from_external_tokens)。

ExternalAuth::resolve183–185 ↗
fn resolve(&self) -> ExternalAuthFuture<'_, Option<ExternalAuthTokens>>

作用:给外部认证提供者一个默认的“立刻看看有没有凭证”的实现。默认什么都不给,具体提供者可以重写。

数据流:没有额外输入 → 返回一个异步结果,内容是 Ok(None) → 不读取也不保存任何凭证。

调用关系:AuthManager::resolve_external_api_key_auth 会调用外部提供者的 resolve;默认实现表示“现在没有可直接用的凭证”。

调用图:外部调用 1 个(pin)。

RefreshTokenError::failed_reason197–202 ↗
fn failed_reason(&self) -> Option<RefreshTokenFailedReason>

作用:从刷新失败里提取明确原因,比如过期、被撤销、被重复使用。临时网络错误没有这种固定原因。

数据流:输入一个 RefreshTokenError → 如果是永久失败就取出 reason,如果是临时失败就返回 None → 不改状态。

调用关系:调用方可以用它决定给用户显示哪类错误,或是否还值得重试。

Error::from206–211 ↗
fn from(err: RefreshTokenError) -> Self

作用:把刷新令牌失败转换成普通输入输出错误。这样上层不用认识专门的错误类型,也能按常规错误处理。

数据流:输入 RefreshTokenError → 永久失败包装成 std::io::Error,临时失败取出原来的 io 错误 → 输出 std::io::Error。

调用关系:persist 和刷新流程需要把专用刷新错误接到 Rust 常见的 io 错误通道时会用到它。

调用图:外部调用 1 个(other)。

CodexAuth::from_auth_dot_json215–280 ↗
async fn from_auth_dot_json(
        codex_home: &Path,
        auth_dot_json: AuthDotJson,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
        chatgpt_base_url: Option<&str>,

作用:把存储里的 auth.json 内容变成程序运行时能用的 CodexAuth。它是从“纸面记录”变成“可用通行证”的转换口。

数据流:输入 Codex 主目录、AuthDotJson、存储模式、ChatGPT 地址、钥匙串后端 → 根据 auth_mode 和字段检查必需凭证,创建客户端或存储对象 → 返回对应的 CodexAuth,缺字段就报错。

调用关系:load_auth 读到凭证后会交给它转换;它会调用 create_client、create_auth_storage、AgentIdentity 或 PersonalAccessToken 的加载逻辑。

调用图:调用 2 个内部函数(create_client, create_auth_storage);被 2 处调用(refresh_failure_is_scoped_to_the_matching_auth_snapshot, load_auth);外部调用 13 个(new, new, to_path_buf, BedrockApiKey, Chatgpt, ChatgptAuthTokens, from_agent_identity_jwt, from_api_key, from_personal_access_token, other (+3 more))。

CodexAuth::from_auth_storage282–297 ↗
async fn from_auth_storage(
        codex_home: &Path,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
        chatgpt_base_url: Option<&str>,
        keyring_backend_kind: AuthKeyringB

作用:从本地认证存储里读取当前登录状态。它是给外部调用者用的简化入口。

数据流:输入 Codex 主目录、存储模式、ChatGPT 地址、钥匙串后端 → 调用 load_auth,但不启用 CODEX_API_KEY 环境变量覆盖 → 返回 Some(CodexAuth) 或 None。

调用关系:登录状态查询、CLI 模式判断和测试会用它;真正的读取顺序由 load_auth 处理。

调用图:调用 1 个内部函数(load_auth);被 5 处调用(run_login_status, load_cli_auth_mode, prefers_apikey_when_config_prefers_apikey_even_with_chatgpt_tokens, missing_auth_json_returns_none, chatgpt_auth_tokens_for_tests)。

CodexAuth::from_agent_identity_jwt299–309 ↗
async fn from_agent_identity_jwt(
        jwt: &str,
        chatgpt_base_url: Option<&str>,
    ) -> std::io::Result<Self>

作用:把 Agent 身份 JWT 令牌变成可用的 AgentIdentity 登录。JWT 可以理解成一张带签名的电子身份证。

数据流:输入 JWT 和可选 ChatGPT 后端地址 → 拉取公钥并验证 JWT → 生成 AgentIdentityAuth → 返回 CodexAuth::AgentIdentity。

调用关系:load_auth 和远程认证加载会用它;它把验证细节交给 verified_agent_identity_record 和 AgentIdentityAuth::load。

调用图:调用 2 个内部函数(load, verified_agent_identity_record);被 3 处调用(load_exec_server_remote_auth_provider, assert_agent_identity_plan_alias, load_auth);外部调用 1 个(AgentIdentity)。

CodexAuth::from_personal_access_token311–315 ↗
async fn from_personal_access_token(access_token: &str) -> std::io::Result<Self>

作用:把个人访问令牌变成可用登录。个人访问令牌就是用户手动生成的一把长期钥匙。

数据流:输入访问令牌字符串 → 调用 PersonalAccessTokenAuth::load 解析并校验 → 返回 CodexAuth::PersonalAccessToken。

调用关系:保存或加载个人令牌时会走这条路径,后续账号信息由 PersonalAccessTokenAuth 提供。

调用图:调用 1 个内部函数(load);外部调用 1 个(PersonalAccessToken)。

CodexAuth::auth_mode317–325 ↗
fn auth_mode(&self) -> AuthMode

作用:告诉外界这份登录属于哪一大类,比如 API Key、ChatGPT、AgentIdentity。它隐藏内部更细的存储差异。

数据流:输入当前 CodexAuth → 按枚举分支映射到 AuthMode → 返回模式值,不改状态。

调用关系:is_api_key_auth、is_personal_access_token_auth 和上层状态显示都会用它。

调用图:被 3 处调用(auth_mode_name, is_api_key_auth, is_personal_access_token_auth)。

CodexAuth::api_auth_mode327–336 ↗
fn api_auth_mode(&self) -> ApiAuthMode

作用:返回更贴近后端协议的认证模式。它能区分普通 ChatGPT 登录和外部 ChatGPT 令牌。

数据流:输入当前 CodexAuth → 按具体变体映射到 ApiAuthMode → 返回模式值。

调用关系:相等比较、是否使用 Codex 后端、插件细节构建等地方会用它做精确判断。

调用图:被 4 处调用(build_remote_plugin_detail, eq, is_chatgpt_auth, uses_codex_backend)。

CodexAuth::is_api_key_auth338–340 ↗
fn is_api_key_auth(&self) -> bool

作用:快速判断当前是不是 API Key 登录。API Key 是直接拿一串密钥访问 API 的方式。

数据流:输入当前 CodexAuth → 调用 auth_mode → 比较是否等于 ApiKey → 返回布尔值。

调用关系:刷新令牌时会跳过 API Key;远程认证兼容性检查也会用它。

调用图:调用 1 个内部函数(auth_mode);被 1 处调用(is_supported_exec_server_remote_auth)。

CodexAuth::is_personal_access_token_auth342–344 ↗
fn is_personal_access_token_auth(&self) -> bool

作用:快速判断当前是不是个人访问令牌登录。这种登录通常不能像 ChatGPT OAuth 那样刷新。

数据流:输入当前 CodexAuth → 调用 auth_mode → 比较是否为 PersonalAccessToken → 返回布尔值。

调用关系:AuthManager 在刷新或未授权恢复时会用它决定是否直接放行或停止恢复。

调用图:调用 1 个内部函数(auth_mode)。

CodexAuth::is_chatgpt_auth346–348 ↗
fn is_chatgpt_auth(&self) -> bool

作用:判断当前登录是否带有 ChatGPT 账号属性。它不只看名字,也包括能映射到 ChatGPT 账号的模式。

数据流:输入 CodexAuth → 取 api_auth_mode → 调用模式上的 has_chatgpt_account 判断 → 返回 true 或 false。

调用关系:远程执行服务会用它确认这种登录是否适合需要 ChatGPT 账号的功能。

调用图:调用 1 个内部函数(api_auth_mode);被 1 处调用(is_supported_exec_server_remote_auth)。

CodexAuth::uses_codex_backend350–352 ↗
fn uses_codex_backend(&self) -> bool

作用:判断这份凭证是否会访问 Codex 后端。不同登录方式可能走不同服务。

数据流:输入 CodexAuth → 取 api_auth_mode → 调用 uses_codex_backend → 返回布尔值。

调用关系:云配置资格判断会用它,避免把不适合的认证方式拿去访问 Codex 后端。

调用图:调用 1 个内部函数(api_auth_mode);被 1 处调用(cloud_config_eligible_auth)。

CodexAuth::is_external_chatgpt_tokens354–356 ↗
fn is_external_chatgpt_tokens(&self) -> bool

作用:判断当前是不是外部应用托管的 ChatGPT 令牌。也就是令牌不是本程序自己刷新,而是问外部来源要。

数据流:输入 CodexAuth → 检查是否匹配 ChatgptAuthTokens 变体 → 返回布尔值。

调用关系:未授权恢复会据此选择 external 模式,而不是走本地 OAuth refresh token。

调用图:外部调用 1 个(matches!)。

CodexAuth::supports_unauthorized_recovery358–360 ↗
fn supports_unauthorized_recovery(&self) -> bool

作用:判断遇到 401 未授权时,这份认证是否支持自动补救。当前主要是 ChatGPT 令牌类支持。

数据流:输入 CodexAuth → 检查是否为 Chatgpt 或 ChatgptAuthTokens → 返回布尔值。

调用关系:UnauthorizedRecovery::has_next 会用它决定还能不能继续恢复。

调用图:外部调用 1 个(matches!)。

CodexAuth::api_key363–372 ↗
fn api_key(&self) -> Option<&str>

作用:如果当前就是 API Key 登录,取出那串 key;否则返回空。它避免调用方误把别的令牌当 API Key。

数据流:输入 CodexAuth → 如果是 ApiKey 就返回内部字符串引用,否则返回 None → 不修改状态。

调用关系:刷新比较逻辑 AuthManager::auths_equal_for_refresh 用它判断 API Key 是否真的相同。

CodexAuth::get_token_data375–385 ↗
fn get_token_data(&self) -> Result<TokenData, std::io::Error>

作用:取出 ChatGPT 令牌包,包括 access token、refresh token 和账号声明。非 ChatGPT 令牌型认证会报错。

数据流:输入 CodexAuth → 读取当前 auth_json → 确认里面有 tokens 和 last_refresh → 返回 TokenData,否则返回错误。

调用关系:get_token 和工作区限制检查会用它;它依赖 get_current_auth_json 拿缓存中的原始登录记录。

调用图:调用 1 个内部函数(get_current_auth_json);被 1 处调用(get_token);外部调用 1 个(other)。

CodexAuth::get_token388–403 ↗
fn get_token(&self) -> Result<String, std::io::Error>

作用:取出可放进请求里的 bearer token,也就是 HTTP 请求里证明身份的那串令牌。不是所有认证都能暴露这种令牌。

数据流:输入 CodexAuth → API Key 返回 key,ChatGPT 返回 access_token,个人令牌返回自身;AgentIdentity 和 Bedrock 返回错误 → 输出字符串或错误。

调用关系:认证提供器构建请求头时会用它;ChatGPT 分支会先调用 get_token_data。

调用图:调用 1 个内部函数(get_token_data);被 1 处调用(auth_provider_from_auth);外部调用 1 个(other)。

CodexAuth::get_account_id406–412 ↗
fn get_account_id(&self) -> Option<String>

作用:取出当前登录对应的账号 ID。账号 ID 用来防止刷新或重载时串到另一个账号。

数据流:输入 CodexAuth → AgentIdentity 和个人令牌直接问自身,ChatGPT 从当前 token 数据里取 → 返回账号 ID 或 None。

调用关系:未授权恢复、工作区检查、身份展示和缓存上下文都会用它。

调用图:调用 1 个内部函数(get_current_token_data);被 5 处调用(connector_directory_cache_context, auth_identity, global, ensure_unlisted_workspace_target, auth_provider_from_auth)。

CodexAuth::is_fedramp_account415–423 ↗
fn is_fedramp_account(&self) -> bool

作用:判断账号是否带 FedRAMP 标记。FedRAMP 是美国政府云安全合规要求,影响某些功能和路由。

数据流:输入 CodexAuth → 从 AgentIdentity、个人令牌或 ChatGPT id token 中读取 FedRAMP 声明 → 返回 true 或 false。

调用关系:请求认证提供器会用它给后续服务传递合规属性。

调用图:调用 1 个内部函数(get_current_token_data);被 1 处调用(auth_provider_from_auth)。

CodexAuth::get_account_email426–432 ↗
fn get_account_email(&self) -> Option<String>

作用:取出当前账号的邮箱。没有邮箱信息的认证方式会返回空。

数据流:输入 CodexAuth → AgentIdentity 和个人令牌直接取邮箱,ChatGPT 从 id token 声明中取 → 返回邮箱或 None。

调用关系:账号展示或日志上下文需要人可读身份时会用它。

调用图:调用 1 个内部函数(get_current_token_data)。

CodexAuth::get_chatgpt_user_id435–443 ↗
fn get_chatgpt_user_id(&self) -> Option<String>

作用:取出 ChatGPT 用户 ID。它比邮箱更适合做稳定的机器识别。

数据流:输入 CodexAuth → 从 AgentIdentity、个人令牌或 ChatGPT token 声明中读取 user id → 返回字符串或 None。

调用关系:身份构建、全局上下文和连接器目录缓存会用它区分用户。

调用图:调用 1 个内部函数(get_current_token_data);被 3 处调用(connector_directory_cache_context, auth_identity, global)。

CodexAuth::account_plan_type448–462 ↗
fn account_plan_type(&self) -> Option<AccountPlanType>

作用:判断账号套餐类型,比如 Free、Plus、Pro、Team 等。产品功能常常要按套餐开关。

数据流:输入 CodexAuth → AgentIdentity 和个人令牌直接给出套餐,ChatGPT 从 id token 的 plan 字段转换 → 返回套餐类型或 None。

调用关系:云配置资格和 is_workspace_account 会用它判断账号能力。

调用图:调用 1 个内部函数(get_current_token_data);被 2 处调用(cloud_config_eligible_auth, is_workspace_account)。

CodexAuth::is_workspace_account464–467 ↗
fn is_workspace_account(&self) -> bool

作用:判断当前账号是不是工作区类账号。工作区账号通常代表团队或企业空间。

数据流:输入 CodexAuth → 调用 account_plan_type → 用套餐类型判断是否为 workspace → 返回布尔值。

调用关系:连接器目录和全局上下文会用它决定是否按工作区账号处理。

调用图:调用 1 个内部函数(account_plan_type);被 2 处调用(connector_directory_cache_context, global)。

CodexAuth::get_current_auth_json470–481 ↗
fn get_current_auth_json(&self) -> Option<AuthDotJson>

作用:取出 ChatGPT 令牌型认证当前缓存的原始 auth.json 内容。其他认证方式没有这种内容。

数据流:输入 CodexAuth → 如果是 Chatgpt 或 ChatgptAuthTokens,就加互斥锁读取缓存副本;否则返回 None → 不改数据。

调用关系:get_token_data、get_current_token_data 和刷新比较逻辑都会用它看当前快照。互斥锁是一把锁,防止两个任务同时读写同一份缓存。

调用图:被 2 处调用(get_current_token_data, get_token_data)。

CodexAuth::get_current_token_data484–486 ↗
fn get_current_token_data(&self) -> Option<TokenData>

作用:从当前缓存的 auth.json 里取出 TokenData。它是 get_current_auth_json 的更窄版本。

数据流:输入 CodexAuth → 先拿 auth_json → 再取其中 tokens 字段 → 返回 TokenData 或 None。

调用关系:账号 ID、邮箱、用户 ID、套餐和 FedRAMP 判断都会通过它读取 ChatGPT token 声明。

调用图:调用 1 个内部函数(get_current_auth_json);被 5 处调用(account_plan_type, get_account_email, get_account_id, get_chatgpt_user_id, is_fedramp_account)。

CodexAuth::create_dummy_chatgpt_auth_for_testing489–517 ↗
fn create_dummy_chatgpt_auth_for_testing() -> Self

作用:为测试制造一份假的 ChatGPT 登录。它让测试不用真的登录也能跑完整流程。

数据流:无输入 → 生成固定 token 数据、临时存储和 HTTP 客户端 → 返回 CodexAuth::Chatgpt。

调用关系:大量测试和测试用会话构造会调用它;它使用 create_client 和 create_auth_storage 搭出最小可用环境。

调用图:调用 3 个内部函数(default, create_client, create_auth_storage);被 139 处调用(capture_file_writes_exact_serialized_request, capture_file_writes_final_batches_as_separate_lines, remote_control_auth_manager, remote_control_auth_manager_with_home, remote_control_auth_manager, guardian_command_execution_notifications_wrap_review_lifecycle, interrupted_subagent_activity_removes_missing_thread_watch, turn_started_omits_active_snapshot_items, effective_mcp_servers_preserve_runtime_servers, expands_cached_remote_plugins_by_loaded_apps (+15 more));外部调用 7 个(new, default, new, from, Chatgpt, now, format!)。

CodexAuth::from_api_key519–523 ↗
fn from_api_key(api_key: &str) -> Self

作用:用一串 API Key 直接创建 CodexAuth。适合环境变量、登录命令或测试快速构造认证。

数据流:输入 API Key 字符串 → 复制保存到 ApiKeyAuth → 返回 CodexAuth::ApiKey。

调用关系:load_auth、外部 API Key 解析、测试构造和很多上层入口都会用它。

调用图:被 65 处调用(refresh_test_state, load_cli_auth_mode, exec_server_remote_auth_accepts_api_key_auth, returns_api_curated_fallback_plugins_for_direct_provider_auth, interrupted_v2_agent_is_lost_after_residency_eviction, residency_slot_reservation_unloads_oldest_idle_v2_agent, new_with_config, list_agent_subtree_thread_ids_finds_live_descendants_of_unloaded_root, resume_agent_from_rollout_does_not_reopen_v2_descendants, resume_agent_releases_slot_after_resume_failure (+15 more));外部调用 1 个(ApiKey)。

ChatgptAuth::current_auth_json527–530 ↗
fn current_auth_json(&self) -> Option<AuthDotJson>

作用:读取 ChatGPT 登录对象里当前缓存的 auth.json。它只服务 ChatgptAuth 这个具体类型。

数据流:输入 ChatgptAuth → 加锁读取 auth_dot_json → 克隆后返回 Option<AuthDotJson>。

调用关系:ChatgptAuth::current_token_data 和主动刷新判断会用它。

调用图:被 1 处调用(current_token_data)。

ChatgptAuth::current_token_data532–534 ↗
fn current_token_data(&self) -> Option<TokenData>

作用:读取 ChatGPT 登录里的当前 token 数据。刷新令牌前需要拿到 refresh token。

数据流:输入 ChatgptAuth → 调用 current_auth_json → 从中取 tokens → 返回 TokenData 或 None。

调用关系:AuthManager::refresh_token_from_authority_impl 在真正刷新 ChatGPT token 前会调用它。

调用图:调用 1 个内部函数(current_auth_json)。

ChatgptAuth::storage536–538 ↗
fn storage(&self) -> &Arc<dyn AuthStorageBackend>

作用:拿到 ChatGPT 登录对应的认证存储位置。刷新后要把新 token 写回这里。

数据流:输入 ChatgptAuth → 返回内部 storage 的引用 → 不读取文件也不修改数据。

调用关系:AuthManager::refresh_and_persist_chatgpt_token 会用它调用 persist_tokens。

调用图:被 1 处调用(refresh_and_persist_chatgpt_token)。

ChatgptAuth::client540–542 ↗
fn client(&self) -> &CodexHttpClient

作用:拿到 ChatGPT 登录使用的 HTTP 客户端。刷新令牌时要用这个客户端请求认证服务。

数据流:输入 ChatgptAuth → 返回内部 CodexHttpClient 引用 → 不发请求。

调用关系:AuthManager::refresh_and_persist_chatgpt_token 会把它交给 request_chatgpt_token_refresh。

调用图:被 1 处调用(refresh_and_persist_chatgpt_token)。

read_openai_api_key_from_env549–554 ↗
fn read_openai_api_key_from_env() -> Option<String>

作用:从 OPENAI_API_KEY 环境变量读取 API Key。环境变量就是操作系统给程序的一张小纸条。

数据流:无输入 → 读取环境变量、去掉前后空格、过滤空字符串 → 返回 key 或 None。

调用关系:这个函数提供通用读取能力;本文件的主加载流程更偏向 CODEX_API_KEY。

调用图:外部调用 1 个(var)。

read_codex_api_key_from_env556–558 ↗
fn read_codex_api_key_from_env() -> Option<String>

作用:从 CODEX_API_KEY 环境变量读取 Codex API Key。它让用户无需写入磁盘也能登录。

数据流:无输入 → 调用 read_non_empty_env_var 读取并清理变量 → 返回 key 或 None。

调用关系:load_auth 在允许环境变量覆盖时会最先检查它。

调用图:调用 1 个内部函数(read_non_empty_env_var);被 1 处调用(load_auth)。

read_codex_access_token_from_env560–562 ↗
fn read_codex_access_token_from_env() -> Option<String>

作用:从 CODEX_ACCESS_TOKEN 环境变量读取访问令牌。它支持个人令牌或 Agent 身份令牌这类外部注入登录。

数据流:无输入 → 调用 read_non_empty_env_var → 返回令牌字符串或 None。

调用关系:load_auth 会在临时存储之后检查它,并再判断令牌类型。

调用图:调用 1 个内部函数(read_non_empty_env_var);被 1 处调用(load_auth)。

read_non_empty_env_var564–569 ↗
fn read_non_empty_env_var(key: &str) -> Option<String>

作用:读取一个环境变量,并确保它不是空白。这样可以避免把空字符串误当成有效密码。

数据流:输入变量名 → 从系统环境读取、trim、过滤空值 → 返回 Some(值) 或 None。

调用关系:read_codex_api_key_from_env 和 read_codex_access_token_from_env 都复用它。

调用图:被 2 处调用(read_codex_access_token_from_env, read_codex_api_key_from_env);外部调用 1 个(var)。

verified_agent_identity_record571–581 ↗
async fn verified_agent_identity_record(
    jwt: &str,
    chatgpt_base_url: &str,
) -> std::io::Result<AgentIdentityAuthRecord>

作用:验证 Agent 身份 JWT,并把它转成可保存的身份记录。它确保这张电子身份证确实由可信方签发。

数据流:输入 JWT 和 ChatGPT 后端地址 → 先做基本解析,再拉取 JWKS 公钥集合并验证签名 → 返回 AgentIdentityAuthRecord 或错误。

调用关系:CodexAuth::from_agent_identity_jwt 和 login_with_access_token 都依赖它阻止伪造身份。

调用图:调用 2 个内部函数(build_reqwest_client, from_agent_identity_jwt);被 2 处调用(from_agent_identity_jwt, login_with_access_token);外部调用 2 个(decode_agent_identity_jwt, fetch_agent_identity_jwks)。

logout585–596 ↗
fn logout(
    codex_home: &Path,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<bool>

作用:删除指定存储里的登录凭证。它相当于把本地保存的通行证撕掉。

数据流:输入 Codex 主目录、存储模式和钥匙串后端 → 创建对应存储 → 调用 delete → 返回是否真的删了东西。

调用关系:logout_all_stores 会调用它来分别清理临时存储和持久存储。

调用图:调用 1 个内部函数(create_auth_storage);被 1 处调用(logout_all_stores);外部调用 1 个(to_path_buf)。

logout_with_revoke598–622 ↗
async fn logout_with_revoke(
    codex_home: &Path,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<bool>

作用:登出前尽量通知 OpenAI 撤销令牌,然后清理本地凭证。撤销失败也不会阻止本地登出。

数据流:输入 Codex 主目录和存储配置 → 尝试读取 auth.json,再调用 revoke_auth_tokens,最后 logout_all_stores → 返回是否删除了本地凭证。

调用关系:给顶层登出命令使用;它会调用 load_auth_dot_json、revoke_auth_tokens 和 logout_all_stores。

调用图:调用 3 个内部函数(load_auth_dot_json, logout_all_stores, revoke_auth_tokens);外部调用 1 个(warn!)。

login_with_api_key625–646 ↗
fn login_with_api_key(
    codex_home: &Path,
    api_key: &str,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<()>

作用:把 API Key 登录写入认证存储。之后程序启动就能直接读到这把 key。

数据流:输入 Codex 主目录、API Key 和存储配置 → 构造只含 API Key 的 AuthDotJson → 调用 save_auth 保存。

调用关系:登录命令会用它;真正写入由 save_auth 和底层 AuthStorageBackend 完成。

调用图:调用 1 个内部函数(save_auth)。

login_with_access_token649–696 ↗
async fn login_with_access_token(
    codex_home: &Path,
    access_token: &str,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    forced_chatgpt_workspace_id: Option<&[String]>,
    chat

作用:用一串访问令牌登录,自动判断它是个人访问令牌还是 Agent 身份 JWT。不同类型会用不同字段保存。

数据流:输入 Codex 主目录、令牌、存储配置、可选工作区限制和后端地址 → 分类令牌,校验个人令牌工作区或验证 Agent JWT → 构造 AuthDotJson 并保存。

调用关系:登录命令处理非 API Key 凭证时会调用它;它会用 classify_codex_access_token、verified_agent_identity_record、ensure_personal_access_token_workspace_allowed 和 save_auth。

调用图:调用 5 个内部函数(classify_codex_access_token, ensure_personal_access_token_workspace_allowed, save_auth, verified_agent_identity_record, load)。

ensure_personal_access_token_workspace_allowed698–704 ↗
fn ensure_personal_access_token_workspace_allowed(
    expected_workspace_ids: Option<&[String]>,
    auth: &PersonalAccessTokenAuth,
) -> std::io::Result<()>

作用:检查个人访问令牌是否属于允许的工作区。管理员限制工作区时,这一步防止用户用别的账号混进来。

数据流:输入期望工作区 ID 列表和 PersonalAccessTokenAuth → 读取令牌账号 ID → 调用服务端通用检查 → 不匹配则返回权限错误。

调用关系:login_with_access_token 和 load_auth 都会在接受个人令牌前调用它。

调用图:调用 2 个内部函数(account_id, ensure_workspace_account_allowed);被 2 处调用(load_auth, login_with_access_token)。

login_with_chatgpt_auth_tokens707–724 ↗
fn login_with_chatgpt_auth_tokens(
    codex_home: &Path,
    access_token: &str,
    chatgpt_account_id: &str,
    chatgpt_plan_type: Option<&str>,
) -> std::io::Result<()>

作用:把外部应用给的 ChatGPT access token 写入临时存储。临时存储表示这份登录只在当前运行环境里优先生效。

数据流:输入 Codex 主目录、access token、ChatGPT 账号 ID 和套餐类型 → 构造外部 AuthDotJson → 用 Ephemeral 模式保存。

调用关系:外部托管 ChatGPT 登录时使用;它通过 AuthDotJson::from_external_access_token 和 save_auth 完成。

调用图:调用 2 个内部函数(default, save_auth);外部调用 1 个(from_external_access_token)。

save_auth727–739 ↗
fn save_auth(
    codex_home: &Path,
    auth: &AuthDotJson,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<()>

作用:把 AuthDotJson 保存到指定认证后端。后端可以是文件、钥匙串或临时内存。

数据流:输入 Codex 主目录、auth 内容、存储模式、钥匙串后端 → 创建存储对象 → 调用 save 写入。

调用关系:API Key 登录、访问令牌登录、外部 ChatGPT 登录和刷新外部凭证后都会调用它。

调用图:调用 1 个内部函数(create_auth_storage);被 5 处调用(login_with_bedrock_api_key, refresh_external_auth, login_with_access_token, login_with_api_key, login_with_chatgpt_auth_tokens);外部调用 1 个(to_path_buf)。

load_auth_dot_json746–757 ↗
fn load_auth_dot_json(
    codex_home: &Path,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<Option<AuthDotJson>>

作用:原样读取存储里的 AuthDotJson,不套环境变量覆盖和额外转换。适合维护和测试场景。

数据流:输入 Codex 主目录和存储配置 → 创建存储对象 → 调用 load → 返回原始 AuthDotJson 或 None。

调用关系:logout_with_revoke 用它先拿到待撤销的令牌;普通生产读取更推荐 AuthManager。

调用图:调用 1 个内部函数(create_auth_storage);被 1 处调用(logout_with_revoke);外部调用 1 个(to_path_buf)。

enforce_login_restrictions769–865 ↗
async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result<()>

作用:执行管理员或配置指定的登录限制,比如必须用 API Key,或只能登录某些 ChatGPT 工作区。

数据流:输入 AuthConfig → 加载当前认证 → 检查登录方式和工作区 ID → 合规则返回 Ok,不合规则清理凭证并返回带说明的错误。

调用关系:启动或配置加载后会调用它;违规时交给 logout_with_message 清理所有相关存储。

调用图:调用 2 个内部函数(load_auth, logout_with_message);外部调用 1 个(format!)。

logout_with_message867–885 ↗
fn logout_with_message(
    codex_home: &Path,
    message: String,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<()

作用:带着一条解释信息强制登出。它既清理凭证,又把“为什么被登出”作为错误返回。

数据流:输入 Codex 主目录、提示消息和存储配置 → 调用 logout_all_stores → 成功或失败都组装错误消息返回。

调用关系:enforce_login_restrictions 在发现登录方式或工作区违规时调用它。

调用图:调用 1 个内部函数(logout_all_stores);被 1 处调用(enforce_login_restrictions);外部调用 2 个(other, format!)。

logout_all_stores887–910 ↗
fn logout_all_stores(
    codex_home: &Path,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
) -> std::io::Result<bool>

作用:同时清理临时凭证和配置的持久凭证。这样不会出现“看似登出,其实临时令牌还在”的情况。

数据流:输入 Codex 主目录和存储配置 → 如果本来就是临时模式只删临时,否则先删临时再删持久 → 返回是否删过任何凭证。

调用关系:登出、撤销登出和强制登出都会用它;它内部多次调用 logout。

调用图:调用 2 个内部函数(default, logout);被 4 处调用(logout, logout_with_revoke, logout_with_message, logout_with_revoke)。

load_auth912–990 ↗
async fn load_auth(
    codex_home: &Path,
    enable_codex_api_key_env: bool,
    auth_credentials_store_mode: AuthCredentialsStoreMode,
    forced_chatgpt_workspace_id: Option<&[String]>,
    chatgp

作用:按优先级加载当前认证。它决定到底用环境变量、临时外部令牌,还是磁盘/钥匙串里的登录。

数据流:输入 Codex 主目录、是否启用 CODEX_API_KEY、存储模式、工作区限制、后端地址、钥匙串后端 → 依次检查 CODEX_API_KEY、临时存储、CODEX_ACCESS_TOKEN、持久存储 → 返回 CodexAuth 或 None。

调用关系:AuthManager 启动、reload、CodexAuth::from_auth_storage 和限制检查都会调用它;它会把原始记录交给 CodexAuth::from_auth_dot_json。

调用图:调用 10 个内部函数(default, classify_codex_access_token, from_agent_identity_jwt, from_api_key, from_auth_dot_json, ensure_personal_access_token_workspace_allowed, read_codex_access_token_from_env, read_codex_api_key_from_env, load, create_auth_storage);被 4 处调用(load_auth_from_storage, new_with_workspace_restriction, from_auth_storage, enforce_login_restrictions);外部调用 2 个(to_path_buf, PersonalAccessToken)。

persist_tokens993–1016 ↗
fn persist_tokens(
    storage: &Arc<dyn AuthStorageBackend>,
    id_token: Option<String>,
    access_token: Option<String>,
    refresh_token: Option<String>,
) -> std::io::Result<AuthDotJson>

作用:把刷新得到的新 ChatGPT token 写回存储,并更新时间戳。它保证下次启动也能用新凭证。

数据流:输入存储对象和可选的新 id_token、access_token、refresh_token → 读取现有 auth,替换传入的字段,更新 last_refresh → 保存并返回新的 AuthDotJson。

调用关系:AuthManager::refresh_and_persist_chatgpt_token 在 OAuth 刷新成功后调用它。

调用图:调用 1 个内部函数(parse_chatgpt_jwt_claims);被 1 处调用(refresh_and_persist_chatgpt_token);外部调用 2 个(now, other)。

request_chatgpt_token_refresh1020–1061 ↗
async fn request_chatgpt_token_refresh(
    refresh_token: String,
    client: &CodexHttpClient,
) -> Result<RefreshResponse, RefreshTokenError>

作用:向认证服务器请求刷新 ChatGPT OAuth token。OAuth 可以理解成“用刷新票换一张新的入场券”的机制。

数据流:输入 refresh token 和 HTTP 客户端 → 构造 JSON 请求发到刷新地址 → 成功解析 RefreshResponse,失败则按响应内容区分永久失败或临时失败。

调用关系:AuthManager::refresh_and_persist_chatgpt_token 调用它;它会用 oauth_client_id、refresh_token_endpoint 和 classify_refresh_token_failure。

调用图:调用 5 个内部函数(post, classify_refresh_token_failure, oauth_client_id, refresh_token_endpoint, try_parse_error_message);被 1 处调用(refresh_and_persist_chatgpt_token);外部调用 5 个(other, format!, Permanent, Transient, error!)。

classify_refresh_token_failure1063–1090 ↗
fn classify_refresh_token_failure(body: &str) -> RefreshTokenFailedError

作用:把刷新 token 的失败响应翻译成用户能理解的原因。比如过期、被用过、被撤销或未知。

数据流:输入后端返回的文本 → 提取错误代码并归一化 → 生成 RefreshTokenFailedError,未知错误还会记录日志。

调用关系:request_chatgpt_token_refresh 在 HTTP 失败时调用它决定错误是否永久。

调用图:调用 2 个内部函数(extract_refresh_token_error_code, new);被 1 处调用(request_chatgpt_token_refresh);外部调用 1 个(warn!)。

extract_refresh_token_error_code1092–1116 ↗
fn extract_refresh_token_error_code(body: &str) -> Option<String>

作用:从后端错误 JSON 里抠出错误代码。它兼容几种不同形状的错误格式。

数据流:输入响应正文字符串 → 尝试按 JSON 解析 → 从 error.code、error 字符串或顶层 code 读取 → 返回代码或 None。

调用关系:classify_refresh_token_failure 依赖它获得可分类的后端错误码。

调用图:被 1 处调用(classify_refresh_token_failure)。

oauth_client_id1135–1140 ↗
fn oauth_client_id() -> String

作用:取得刷新 OAuth token 时使用的客户端 ID。测试或特殊部署可以用环境变量覆盖默认值。

数据流:无输入 → 读取 CLIENT_ID_OVERRIDE_ENV_VAR,非空则使用它,否则使用内置 CLIENT_ID → 返回字符串。

调用关系:request_chatgpt_token_refresh 构造刷新请求时会调用它。

调用图:被 2 处调用(request_chatgpt_token_refresh, client_id);外部调用 1 个(var)。

refresh_token_endpoint1142–1145 ↗
fn refresh_token_endpoint() -> String

作用:取得刷新 token 的服务器地址。正常用默认地址,测试或内部环境可通过环境变量改掉。

数据流:无输入 → 读取 REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR,失败则返回默认 REFRESH_TOKEN_URL → 输出 URL 字符串。

调用关系:request_chatgpt_token_refresh 发 HTTP 请求前会调用它。

调用图:被 1 处调用(request_chatgpt_token_refresh);外部调用 1 个(var)。

AuthDotJson::from_external_tokens1148–1179 ↗
fn from_external_tokens(external: &ExternalAuthTokens) -> std::io::Result<Self>

作用:把外部应用提供的 ChatGPT token 包转换成本程序能读的 AuthDotJson。它补齐账号 ID 和套餐信息。

数据流:输入 ExternalAuthTokens → 要求存在 ChatGPT 元数据,解析 access token 的 JWT 声明,填入账号和套餐 → 返回 ChatgptAuthTokens 模式的 AuthDotJson。

调用关系:AuthManager::refresh_external_auth 成功拿到外部新令牌后会用它写入临时存储。

调用图:调用 2 个内部函数(chatgpt_metadata, parse_chatgpt_jwt_claims);外部调用 4 个(Unknown, new, now, other)。

AuthDotJson::from_external_access_token1181–1192 ↗
fn from_external_access_token(
        access_token: &str,
        chatgpt_account_id: &str,
        chatgpt_plan_type: Option<&str>,
    ) -> std::io::Result<Self>

作用:用 access token、账号 ID 和套餐类型快速创建外部 ChatGPT 登录记录。

数据流:输入 access token、ChatGPT 账号 ID、可选套餐 → 先构造 ExternalAuthTokens::chatgpt → 再转成 AuthDotJson。

调用关系:login_with_chatgpt_auth_tokens 用它把外部令牌落到临时存储。

调用图:调用 1 个内部函数(chatgpt);外部调用 1 个(from_external_tokens)。

AuthDotJson::resolved_mode1194–1208 ↗
fn resolved_mode(&self) -> ApiAuthMode

作用:推断 AuthDotJson 实际代表哪种登录方式。老版本文件可能没有 auth_mode,所以要从字段反推。

数据流:输入 AuthDotJson → 如果 auth_mode 存在直接返回;否则按 personal token、Bedrock key、API key、ChatGPT 的顺序推断 → 返回 ApiAuthMode。

调用关系:CodexAuth::from_auth_dot_json 和 storage_mode 会用它决定怎么解释这份凭证。

调用图:被 1 处调用(storage_mode)。

AuthDotJson::storage_mode1210–1219 ↗
fn storage_mode(
        &self,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
    ) -> AuthCredentialsStoreMode

作用:决定这份 AuthDotJson 应该按哪种存储模式处理。外部 ChatGPT tokens 强制走临时存储。

数据流:输入 AuthDotJson 和默认存储模式 → 如果 resolved_mode 是 ChatgptAuthTokens 返回 Ephemeral,否则返回传入模式。

调用关系:CodexAuth::from_auth_dot_json 创建 ChatGPT 存储对象前会用它。

调用图:调用 1 个内部函数(resolved_mode)。

CachedAuth::fmt1238–1252 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:给缓存认证状态生成调试输出。它只显示模式和失败原因,避免把秘密令牌打印出来。

数据流:输入 CachedAuth 和格式化器 → 写入 auth_mode 与 permanent_refresh_failure 的 reason → 返回格式化结果。

调用关系:AuthManager 的 Debug 输出会间接用它,方便排查但不泄露凭证。

调用图:外部调用 1 个(debug_struct)。

UnauthorizedRecoveryStepResult::auth_state_changed1308–1310 ↗
fn auth_state_changed(&self) -> Option<bool>

作用:告诉调用方本次未授权恢复步骤有没有改变认证状态。调用方可据此决定是否重试请求。

数据流:输入结果对象 → 返回内部 auth_state_changed 字段 → 不修改状态。

调用关系:UnauthorizedRecovery::next 返回这个对象,上层 401 处理逻辑读取它。

UnauthorizedRecovery::new1314–1336 ↗
fn new(manager: Arc<AuthManager>) -> Self

作用:创建一个 401 未授权恢复状态机。状态机就是按固定步骤推进的小流程。

数据流:输入共享 AuthManager → 读取当前缓存账号 ID,判断走 managed 还是 external 模式,设置第一步 → 返回 UnauthorizedRecovery。

调用关系:AuthManager::unauthorized_recovery 会调用它;后续由 has_next 和 next 推进。

调用图:被 1 处调用(unauthorized_recovery)。

UnauthorizedRecovery::has_next1338–1357 ↗
fn has_next(&self) -> bool

作用:判断还有没有下一步补救可做。没有时就该把 401 错误交给用户或上层。

数据流:输入当前恢复对象 → 检查外部 API Key、当前认证是否支持恢复、外部提供者是否存在、步骤是否 Done → 返回布尔值。

调用关系:handle_unauthorized、recover_remote_control_auth 和 next 都会用它避免重复恢复。

调用图:被 3 处调用(recover_remote_control_auth, handle_unauthorized, next);外部调用 1 个(matches!)。

UnauthorizedRecovery::unavailable_reason1359–1395 ↗
fn unavailable_reason(&self) -> &'static str

作用:给出不能继续恢复的简短原因。它方便日志或遥测知道卡在哪里。

数据流:输入当前恢复对象 → 按认证类型、外部认证是否存在、步骤是否结束判断 → 返回静态字符串原因。

调用关系:上层可在 has_next 为 false 时调用它记录更明确的状态。

调用图:外部调用 1 个(matches!)。

UnauthorizedRecovery::mode_name1397–1402 ↗
fn mode_name(&self) -> &'static str

作用:返回恢复模式名称:managed 或 external。managed 是本程序管令牌,external 是外部系统管令牌。

数据流:输入恢复对象 → 根据 mode 枚举返回字符串 → 不改状态。

调用关系:远程控制恢复流程会用它做日志或事件标记。

调用图:被 1 处调用(recover_remote_control_auth)。

UnauthorizedRecovery::step_name1404–1411 ↗
fn step_name(&self) -> &'static str

作用:返回当前恢复步骤名称,比如 reload、refresh_token、external_refresh。方便观察流程进度。

数据流:输入恢复对象 → 根据 step 枚举返回字符串 → 不改状态。

调用关系:远程控制恢复流程会用它记录当前尝试到哪一步。

调用图:被 1 处调用(recover_remote_control_auth)。

UnauthorizedRecovery::next1413–1470 ↗
async fn next(&mut self) -> Result<UnauthorizedRecoveryStepResult, RefreshTokenError>

作用:执行下一步 401 恢复动作。它可能重读本地凭证、刷新 token,或请求外部认证刷新。

数据流:输入可变的恢复对象 → 先检查 has_next,再按当前 step 调用 AuthManager 的重载或刷新方法,并推进 step → 返回步骤结果或刷新错误。

调用关系:handle_unauthorized 和 recover_remote_control_auth 在请求遇到 401 后反复调用它。

调用图:调用 2 个内部函数(has_next, new);被 2 处调用(recover_remote_control_auth, handle_unauthorized);外部调用 1 个(Permanent)。

AuthManager::fmt1518–1535 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:给 AuthManager 生成安全的调试信息。它展示配置和是否有外部认证,但不打印秘密凭证。

数据流:输入 AuthManager 和格式化器 → 读取若干字段和 has_external_auth → 写入调试结构 → 返回格式化结果。

调用关系:日志或调试输出 AuthManager 时自动使用;内部调用 has_external_auth。

调用图:调用 1 个内部函数(has_external_auth);外部调用 1 个(debug_struct)。

AuthManager::new1543–1559 ↗
async fn new(
        codex_home: PathBuf,
        enable_codex_api_key_env: bool,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
        chatgpt_base_url: Option<String>,
        keyr

作用:创建认证管理器并加载初始凭证。它是运行时认证缓存的普通构造入口。

数据流:输入 Codex 主目录、是否启用环境 API Key、存储模式、ChatGPT 地址、钥匙串后端 → 转交 new_with_workspace_restriction,工作区限制为空 → 返回 AuthManager。

调用关系:测试和加载管理器时会调用它;实际初始化细节在 new_with_workspace_restriction。

调用图:被 12 处调用(auth_manager_with_api_key, auth_manager_with_plan_and_identity, get_bundle_recovers_after_unauthorized_reload, get_bundle_recovers_after_unauthorized_reload_updates_cache_identity, get_bundle_surfaces_auth_recovery_message, get_bundle_unauthorized_without_recovery_uses_generic_message, load_auth_manager, personal_access_token_does_not_offer_unauthorized_recovery, bedrock_only_auth_storage_creates_primary_auth, login_with_bedrock_api_key_replaces_openai_auth (+2 more));外部调用 1 个(new_with_workspace_restriction)。

AuthManager::new_with_workspace_restriction1561–1596 ↗
async fn new_with_workspace_restriction(
        codex_home: PathBuf,
        enable_codex_api_key_env: bool,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
        forced_chatgpt_work

作用:创建带工作区限制的 AuthManager。启动时会尽量加载凭证,失败就当未登录。

数据流:输入目录、环境变量开关、存储模式、工作区限制、后端地址、钥匙串后端 → 调用 load_auth,建立缓存、watch 通知通道、刷新锁和外部认证槽 → 返回 AuthManager。

调用关系:AuthManager::new 和 shared_from_config 依赖它;它是 AuthManager 的真正初始化工厂。

调用图:调用 1 个内部函数(load_auth);被 2 处调用(auth_manager_rejects_env_personal_access_token_workspace_mismatch, auth_manager_rejects_stored_personal_access_token_workspace_mismatch);外部调用 3 个(new, new, channel)。

AuthManager::from_auth_for_testing1599–1618 ↗
fn from_auth_for_testing(auth: CodexAuth) -> Arc<Self>

作用:用指定 CodexAuth 创建测试用 AuthManager。它绕过真实磁盘加载。

数据流:输入 CodexAuth → 放入 CachedAuth,创建通知通道和默认配置 → 返回 Arc<AuthManager>。

调用关系:大量单元测试和测试会话构造会用它,避免依赖真实用户登录。

调用图:调用 1 个内部函数(default);被 42 处调用(refresh_test_state, model_client_with_counting_attestation, emit_subagent_session_started_includes_fork_lineage_from_session_configuration, guardian_subagent_does_not_inherit_parent_exec_policy_rules, make_session_and_context, make_session_and_context_with_auth_config_home_and_rx, make_session_with_config_and_rx, make_session_with_history_source_and_agent_control_and_rx, session_new_fails_when_zsh_fork_enabled_without_packaged_zsh, auth_manager_from_auth (+15 more));外部调用 5 个(new, from, new, new, channel)。

AuthManager::from_auth_for_testing_with_home1621–1639 ↗
fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<Self>

作用:创建带指定 Codex home 的测试 AuthManager。适合测试需要真实路径但凭证由测试指定的场景。

数据流:输入 CodexAuth 和目录 → 构造缓存和默认配置,只把 codex_home 设为传入路径 → 返回 Arc<AuthManager>。

调用关系:auth_manager_from_auth_with_home 等测试会用它验证涉及文件位置的逻辑。

调用图:调用 1 个内部函数(default);被 1 处调用(auth_manager_from_auth_with_home);外部调用 4 个(new, new, new, channel)。

AuthManager::external_bearer_only1641–1660 ↗
fn external_bearer_only(config: ModelProviderAuthInfo) -> Arc<Self>

作用:创建一个只靠外部 bearer token 的 AuthManager。bearer token 就是请求头里“持票入场”的令牌。

数据流:输入模型提供商认证配置 → 不设置本地 CodexAuth,创建 BearerTokenRefresher 作为 external_auth → 返回 Arc<AuthManager>。

调用关系:自定义模型提供商和相关测试会用它;未授权恢复会走外部刷新路径。

调用图:调用 2 个内部函数(default, new);被 5 处调用(external_bearer_only_auth_manager_disables_auto_refresh_when_interval_is_zero, external_bearer_only_auth_manager_returns_none_when_command_fails, external_bearer_only_auth_manager_uses_cached_provider_token, unauthorized_recovery_uses_external_refresh_for_bearer_manager, auth_manager_for_provider);外部调用 5 个(new, from, new, new, channel)。

AuthManager::auth_cached1663–1665 ↗
fn auth_cached(&self) -> Option<CodexAuth>

作用:直接取当前缓存的认证,不触发刷新。它像看一眼柜台当前放着哪张通行证。

数据流:输入 AuthManager → 读 RwLock 缓存并克隆 auth → 返回 Option<CodexAuth>。

调用关系:auth、刷新、登出、模式查询、外部刷新和恢复流程都会频繁调用它。RwLock 是读写锁,允许多人读但写入时独占。

调用图:被 9 处调用(auth, auth_mode, get_api_auth_mode, is_external_chatgpt_auth_active, logout_with_revoke, refresh_external_auth, refresh_token, refresh_token_from_authority_impl, reload_if_account_id_matches)。

AuthManager::auth_change_receiver1668–1670 ↗
fn auth_change_receiver(&self) -> watch::Receiver<u64>

作用:订阅认证状态变化通知。别人可以用它知道 token 被换了,应该停止旧请求或重试。

数据流:输入 AuthManager → 从 watch 通道创建一个接收端 → 返回 Receiver。

调用关系:当 set_cached_auth 发现刷新相关身份变化时,会通过这个通道递增版本号。

调用图:外部调用 1 个(subscribe)。

AuthManager::refresh_failure_for_auth1672–1680 ↗
fn refresh_failure_for_auth(&self, auth: &CodexAuth) -> Option<RefreshTokenFailedError>

作用:查询某份认证是否已经有永久刷新失败记录。这样同一张坏票不会反复请求服务器。

数据流:输入 CodexAuth 引用 → 读取缓存中的 permanent_refresh_failure,并确认它对应同一认证快照 → 返回失败错误或 None。

调用关系:refresh_token_from_authority_impl 在真正刷新前会调用它实现快速失败。

调用图:被 1 处调用(refresh_token_from_authority_impl)。

AuthManager::auth1685–1698 ↗
async fn auth(&self) -> Option<CodexAuth>

作用:获取当前可用认证,必要时会主动刷新快过期的 ChatGPT token。它是业务代码最常用的取认证入口。

数据流:输入 AuthManager → 先尝试解析外部 API Key,再取缓存;如果需要主动刷新则调用 refresh_token → 最后返回最新缓存或原认证。

调用关系:发送追踪事件、检查限流等运行时请求会调用它;它串起 resolve_external_api_key_auth、auth_cached 和 refresh_token。

调用图:调用 3 个内部函数(auth_cached, refresh_token, resolve_external_api_key_auth);被 2 处调用(send_track_events, rate_limits_check);外部调用 2 个(should_refresh_proactively, error!)。

AuthManager::reload1702–1706 ↗
async fn reload(&self) -> bool

作用:强制从存储重新加载认证,并更新内存缓存。适合登录、登出、刷新后让全程序看到新状态。

数据流:输入 AuthManager → 调用 load_auth_from_storage → 用 set_cached_auth 更新缓存 → 返回认证是否变化。

调用关系:登出、撤销登出、外部刷新和 ChatGPT token 持久化后都会调用它。

调用图:调用 2 个内部函数(load_auth_from_storage, set_cached_auth);被 4 处调用(logout, logout_with_revoke, refresh_and_persist_chatgpt_token, refresh_external_auth);外部调用 1 个(info!)。

AuthManager::reload_if_account_id_matches1708–1741 ↗
async fn reload_if_account_id_matches(
        &self,
        expected_account_id: Option<&str>,
    ) -> ReloadOutcome

作用:只在账号 ID 没变时重载认证。这样可以防止另一个进程切换账号后,本进程误刷新错账号。

数据流:输入期望账号 ID → 没有期望值则跳过;加载存储认证并比较账号 ID;匹配则更新缓存并返回是否变化,不匹配则跳过。

调用关系:refresh_token 和 UnauthorizedRecovery::next 在刷新前会用它做安全保护。

调用图:调用 3 个内部函数(auth_cached, load_auth_from_storage, set_cached_auth);被 1 处调用(refresh_token);外部调用 2 个(auths_equal_for_refresh, info!)。

AuthManager::auths_equal_for_refresh1743–1764 ↗
fn auths_equal_for_refresh(a: Option<&CodexAuth>, b: Option<&CodexAuth>) -> bool

作用:判断两份认证在“刷新安全性”上是否相同。它比普通相等更严格,会比较 token 快照或身份记录。

数据流:输入两份可选 CodexAuth → 按认证模式分别比较 API Key、ChatGPT auth_json、Agent 记录、个人令牌或 Bedrock Key → 返回布尔值。

调用关系:reload_if_account_id_matches、set_cached_auth 和永久失败记录都用它决定是否同一张凭证。

AuthManager::auths_equal1766–1772 ↗
fn auths_equal(a: Option<&CodexAuth>, b: Option<&CodexAuth>) -> bool

作用:判断两份可选认证是否普通意义上相等。它用于告诉外界认证模式或内容有没有变化。

数据流:输入两个 Option<CodexAuth> → 两个都空算相等,两个都有则调用 CodexAuth 的相等比较,否则不相等 → 返回布尔值。

调用关系:set_cached_auth 用它计算 changed,作为 reload 的返回结果。

调用图:被 1 处调用(set_cached_auth)。

AuthManager::record_permanent_refresh_failure_if_unchanged1776–1791 ↗
fn record_permanent_refresh_failure_if_unchanged(
        &self,
        attempted_auth: &CodexAuth,
        error: &RefreshTokenFailedError,
    )

作用:记录某次刷新永久失败,但只在当前缓存仍是当时那份认证时记录。这样换账号后不会继承旧错误。

数据流:输入尝试刷新的认证和错误 → 加写锁,比较当前认证是否仍匹配 → 匹配就保存 AuthScopedRefreshFailure。

调用关系:refresh_token_from_authority_impl 捕获永久错误后调用它;比较逻辑来自 auths_equal_for_refresh。

调用图:被 1 处调用(refresh_token_from_authority_impl);外部调用 3 个(auths_equal_for_refresh, clone, clone)。

AuthManager::load_auth_from_storage1793–1806 ↗
async fn load_auth_from_storage(&self) -> Option<CodexAuth>

作用:按 AuthManager 当前配置从存储加载认证。它把成员变量打包传给通用 load_auth。

数据流:输入 AuthManager → 读取当前工作区限制 → 调用 load_auth,失败当作 None → 返回 Option<CodexAuth>。

调用关系:reload 和 reload_if_account_id_matches 通过它获取最新存储快照。

调用图:调用 2 个内部函数(forced_chatgpt_workspace_id, load_auth);被 2 处调用(reload, reload_if_account_id_matches)。

AuthManager::set_cached_auth1808–1826 ↗
fn set_cached_auth(&self, new_auth: Option<CodexAuth>) -> bool

作用:更新内存里的认证缓存,并在刷新相关身份变化时通知订阅者。它是缓存换挡的统一出口。

数据流:输入新的认证 Option → 加写锁,比较普通变化和刷新相关变化;必要时清掉永久失败记录并递增通知版本 → 写入新缓存,返回普通变化结果。

调用关系:reload 和 reload_if_account_id_matches 都通过它修改缓存;auth_change_receiver 的接收者会收到变化。

调用图:调用 1 个内部函数(auths_equal);被 2 处调用(reload, reload_if_account_id_matches);外部调用 3 个(auths_equal_for_refresh, send_modify, info!)。

AuthManager::set_external_auth1828–1832 ↗
fn set_external_auth(&self, external_auth: Arc<dyn ExternalAuth>)

作用:设置外部认证提供者。设置后 AuthManager 可以向外部程序要 API Key 或刷新令牌。

数据流:输入 Arc<dyn ExternalAuth> → 加写锁写入 external_auth 字段 → 不返回数据。

调用关系:宿主应用接入自管认证时会调用它;auth 和 refresh_external_auth 后续会读取这个提供者。

AuthManager::clear_external_auth1834–1838 ↗
fn clear_external_auth(&self)

作用:清除外部认证提供者。之后外部刷新和外部 API Key 解析都不可用。

数据流:输入 AuthManager → 加写锁把 external_auth 设为 None → 不返回数据。

调用关系:测试或运行时关闭外部认证时使用;has_external_auth 会随之变为 false。

AuthManager::set_forced_chatgpt_workspace_id1840–1846 ↗
fn set_forced_chatgpt_workspace_id(&self, workspace_id: Option<Vec<String>>)

作用:设置或更新允许的 ChatGPT 工作区 ID 列表。它用于运行时收紧登录范围。

数据流:输入可选工作区 ID 列表 → 加写锁,若和旧值不同则替换 → 不返回数据。

调用关系:load_auth_from_storage 和 refresh_external_auth 会读取这个限制来校验凭证。

AuthManager::forced_chatgpt_workspace_id1848–1853 ↗
fn forced_chatgpt_workspace_id(&self) -> Option<Vec<String>>

作用:读取当前强制工作区限制。返回克隆值,避免调用者直接改内部状态。

数据流:输入 AuthManager → 加读锁读取 Option<Vec<String>> 并克隆 → 返回限制或 None。

调用关系:load_auth_from_storage 和 refresh_external_auth 会调用它。

调用图:被 2 处调用(load_auth_from_storage, refresh_external_auth)。

AuthManager::has_external_auth1855–1857 ↗
fn has_external_auth(&self) -> bool

作用:判断是否配置了外部认证提供者。它只说明有来源,不说明当前一定有令牌。

数据流:输入 AuthManager → 调用 external_auth → 返回是否为 Some。

调用关系:Debug 输出和 UnauthorizedRecovery 会用它判断外部恢复是否可行。

调用图:调用 1 个内部函数(external_auth);被 1 处调用(fmt)。

AuthManager::is_external_chatgpt_auth_active1859–1863 ↗
fn is_external_chatgpt_auth_active(&self) -> bool

作用:判断当前缓存里是否正在使用外部托管的 ChatGPT tokens。

数据流:输入 AuthManager → 读取 auth_cached → 调用 CodexAuth::is_external_chatgpt_tokens → 返回布尔值。

调用关系:上层可以用它区分本地 OAuth 登录和宿主应用提供的 ChatGPT 登录。

调用图:调用 1 个内部函数(auth_cached)。

AuthManager::codex_api_key_env_enabled1865–1867 ↗
fn codex_api_key_env_enabled(&self) -> bool

作用:告诉外界 CODEX_API_KEY 环境变量覆盖是否开启。这个开关会影响认证优先级。

数据流:输入 AuthManager → 返回 enable_codex_api_key_env 字段 → 不改状态。

调用关系:配置或状态展示可以用它解释为什么环境变量中的 key 生效。

AuthManager::shared1870–1887 ↗
async fn shared(
        codex_home: PathBuf,
        enable_codex_api_key_env: bool,
        auth_credentials_store_mode: AuthCredentialsStoreMode,
        chatgpt_base_url: Option<String>,
        k

作用:创建放在 Arc 里的 AuthManager。Arc 是线程安全引用计数指针,方便多个任务共享同一个管理器。

数据流:输入 AuthManager 构造参数 → 调用 AuthManager::new → 用 Arc 包起来 → 返回 Arc<AuthManager>。

调用关系:远程控制和运行时组件创建共享认证管理器时常用它。

调用图:被 13 处调用(list_remote_control_clients_recovers_auth_after_unauthorized, list_remote_control_clients_retries_unauthorized_only_once, remote_control_handle_discards_pairing_response_after_auth_change, remote_control_handle_recovers_auth_before_refreshing_pairing, persisted_enable_does_not_follow_auth_to_an_account_without_a_preference, remote_control_start_allows_missing_auth_when_enabled, remote_control_waits_for_account_id_before_enrolling, connect_remote_control_websocket_recovers_after_unauthorized_enrollment, connect_remote_control_websocket_recovers_after_unauthorized_refresh, connect_remote_control_websocket_requires_chatgpt_auth (+3 more));外部调用 2 个(new, new)。

AuthManager::shared_from_config1890–1905 ↗
async fn shared_from_config(
        config: &impl AuthManagerConfig,
        enable_codex_api_key_env: bool,
    ) -> Arc<Self>

作用:从已解析配置创建共享 AuthManager。这样调用者不用手动拆出每个认证配置字段。

数据流:输入实现 AuthManagerConfig 的配置对象和环境 API Key 开关 → 读取目录、存储模式、工作区限制、后端地址、钥匙串后端 → 构造 Arc<AuthManager>。

调用关系:主程序启动、测试处理器、远程认证加载等入口会调用它。

调用图:被 15 处调用(start_uninitialized, build_test_processor, run_main_with_transport_options, chatgpt_get_request_with_timeout, apps_enabled, connector_auth, build_report, load_exec_server_remote_auth, run_debug_models_command, cached_directory_connectors_for_tool_suggest_with_auth (+5 more));外部调用 7 个(new, new_with_workspace_restriction, auth_keyring_backend_kind, chatgpt_base_url, cli_auth_credentials_store_mode, codex_home, forced_chatgpt_workspace_id)。

AuthManager::unauthorized_recovery1907–1909 ↗
fn unauthorized_recovery(self: &Arc<Self>) -> UnauthorizedRecovery

作用:为当前 AuthManager 创建一个 401 恢复流程对象。每次遇到未授权错误时可以新建一个。

数据流:输入 Arc<AuthManager> 自身 → 克隆 Arc → 调用 UnauthorizedRecovery::new → 返回恢复状态机。

调用关系:HTTP 请求处理或远程控制在遇到 401 后会调用它,再用 next 推进恢复。

调用图:调用 1 个内部函数(new);外部调用 1 个(clone)。

AuthManager::external_auth1911–1916 ↗
fn external_auth(&self) -> Option<Arc<dyn ExternalAuth>>

作用:取出当前外部认证提供者的共享引用。没有配置时返回空。

数据流:输入 AuthManager → 加读锁读取 external_auth 并克隆 Arc → 返回 Option<Arc<dyn ExternalAuth>>。

调用关系:external_auth_mode、has_external_auth、resolve_external_api_key_auth 和 refresh_external_auth 都通过它访问外部提供者。

调用图:被 4 处调用(external_auth_mode, has_external_auth, refresh_external_auth, resolve_external_api_key_auth)。

AuthManager::external_auth_mode1918–1922 ↗
fn external_auth_mode(&self) -> Option<AuthMode>

作用:读取外部认证提供者声明的认证模式。比如它提供 API Key 还是 ChatGPT token。

数据流:输入 AuthManager → 调用 external_auth → 如果存在就调用其 auth_mode → 返回 AuthMode 或 None。

调用关系:has_external_api_key_auth 用它判断是否应把外部认证当 API Key。

调用图:调用 1 个内部函数(external_auth);被 1 处调用(has_external_api_key_auth)。

AuthManager::has_external_api_key_auth1924–1926 ↗
fn has_external_api_key_auth(&self) -> bool

作用:判断外部认证提供者是否提供 API Key。外部 API Key 会优先影响 auth_mode 和 get_api_auth_mode。

数据流:输入 AuthManager → 调用 external_auth_mode → 比较是否为 AuthMode::ApiKey → 返回布尔值。

调用关系:auth、auth_mode、get_api_auth_mode 和未授权恢复都会参考它。

调用图:调用 1 个内部函数(external_auth_mode);被 3 处调用(auth_mode, get_api_auth_mode, resolve_external_api_key_auth)。

AuthManager::resolve_external_api_key_auth1928–1943 ↗
async fn resolve_external_api_key_auth(&self) -> Option<CodexAuth>

作用:尝试从外部认证提供者立刻拿到 API Key 并包装成 CodexAuth。拿不到就继续使用本地缓存。

数据流:输入 AuthManager → 确认外部模式是 API Key,取出 external_auth,调用 resolve → 有令牌则 CodexAuth::from_api_key,否则返回 None,错误会记录日志。

调用关系:AuthManager::auth 每次取认证时先调用它,让外部 API Key 优先于本地凭证。

调用图:调用 3 个内部函数(external_auth, has_external_api_key_auth, from_api_key);被 1 处调用(auth);外部调用 1 个(error!)。

AuthManager::refresh_token1950–1984 ↗
async fn refresh_token(&self) -> Result<(), RefreshTokenError>

作用:安全地刷新当前 token。它先重读存储,确认没人已经刷新或换账号,再决定是否请求认证服务器。

数据流:输入 AuthManager → 获取刷新信号量防止并发刷新;API Key 和个人令牌直接返回;读取当前账号 ID,调用 reload_if_account_id_matches;没变化才 refresh_token_from_authority_impl → 返回成功或刷新错误。

调用关系:AuthManager::auth 主动刷新时会调用它;内部用信号量(一张通行证)确保同一时间只有一个刷新任务。

调用图:调用 4 个内部函数(auth_cached, refresh_token_from_authority_impl, reload_if_account_id_matches, new);被 1 处调用(auth);外部调用 3 个(acquire, Permanent, info!)。

AuthManager::refresh_token_from_authority1990–1998 ↗
async fn refresh_token_from_authority(&self) -> Result<(), RefreshTokenError>

作用:直接向令牌签发方刷新当前认证,不做前置重载判断。适合恢复流程已经决定要刷新时使用。

数据流:输入 AuthManager → 获取刷新锁 → 调用 refresh_token_from_authority_impl → 返回结果。

调用关系:UnauthorizedRecovery::next 的 refresh_token 步骤会调用它。

调用图:调用 1 个内部函数(refresh_token_from_authority_impl);外部调用 1 个(acquire)。

AuthManager::refresh_token_from_authority_impl2000–2035 ↗
async fn refresh_token_from_authority_impl(&self) -> Result<(), RefreshTokenError>

作用:执行真正的刷新分派。ChatGPT 本地登录走 OAuth refresh token,外部 ChatGPT tokens 则问外部提供者。

数据流:输入 AuthManager → 读取缓存认证,检查是否已有永久失败;按认证类型刷新或跳过;永久失败时记录到缓存 → 返回结果。

调用关系:refresh_token 和 refresh_token_from_authority 都调用它;它会转给 refresh_and_persist_chatgpt_token 或 refresh_external_auth。

调用图:调用 5 个内部函数(auth_cached, record_permanent_refresh_failure_if_unchanged, refresh_and_persist_chatgpt_token, refresh_external_auth, refresh_failure_for_auth);被 2 处调用(refresh_token, refresh_token_from_authority);外部调用 2 个(Permanent, info!)。

AuthManager::logout2041–2050 ↗
async fn logout(&self) -> std::io::Result<bool>

作用:登出并刷新内存缓存。删除本地凭证后,其他组件会立刻看到未登录状态。

数据流:输入 AuthManager → 调用 logout_all_stores 删除凭证 → 调用 reload 清空或更新缓存 → 返回是否删除过凭证。

调用关系:运行时登出入口会用它;它不负责通知服务器撤销令牌。

调用图:调用 2 个内部函数(reload, logout_all_stores)。

AuthManager::logout_with_revoke2052–2067 ↗
async fn logout_with_revoke(&self) -> std::io::Result<bool>

作用:撤销令牌后登出,并刷新缓存。即使撤销失败,也会继续删除本地凭证。

数据流:输入 AuthManager → 从缓存取当前 ChatGPT auth_json,调用 revoke_auth_tokens,删除所有存储,再 reload → 返回删除结果。

调用关系:需要更彻底安全登出时使用;它和顶层 logout_with_revoke 类似,但基于当前管理器缓存。

调用图:调用 4 个内部函数(auth_cached, reload, logout_all_stores, revoke_auth_tokens);外部调用 1 个(warn!)。

AuthManager::get_api_auth_mode2069–2074 ↗
fn get_api_auth_mode(&self) -> Option<ApiAuthMode>

作用:取得当前 API 层认证模式。外部 API Key 会优先显示为 ApiKey。

数据流:输入 AuthManager → 如果外部 API Key 生效返回 ApiKey,否则读取 auth_cached 并取 api_auth_mode → 返回 Option<ApiAuthMode>。

调用关系:current_auth_uses_codex_backend 会调用它判断当前是否走 Codex 后端。

调用图:调用 2 个内部函数(auth_cached, has_external_api_key_auth);被 1 处调用(current_auth_uses_codex_backend)。

AuthManager::auth_mode2076–2081 ↗
fn auth_mode(&self) -> Option<AuthMode>

作用:取得当前面向应用的认证大类。它比 get_api_auth_mode 更适合 UI 或配置判断。

数据流:输入 AuthManager → 外部 API Key 优先返回 ApiKey,否则读取缓存 auth_mode → 返回 Option<AuthMode>。

调用关系:状态显示和权限判断可以调用它了解当前登录方式。

调用图:调用 2 个内部函数(auth_cached, has_external_api_key_auth)。

AuthManager::current_auth_uses_codex_backend2083–2086 ↗
fn current_auth_uses_codex_backend(&self) -> bool

作用:判断当前认证是否会使用 Codex 后端。没有认证或不适用时返回 false。

数据流:输入 AuthManager → 调用 get_api_auth_mode → 调用模式的 uses_codex_backend → 返回布尔值。

调用关系:需要决定是否访问 Codex 云能力的地方会用它。

调用图:调用 1 个内部函数(get_api_auth_mode)。

AuthManager::should_refresh_proactively2088–2110 ↗
fn should_refresh_proactively(auth: &CodexAuth) -> bool

作用:判断 ChatGPT token 是否快过期,是否应该提前刷新。提前刷新能避免请求半路遇到 401。

数据流:输入 CodexAuth → 只处理 Chatgpt;优先解析 access token 的过期时间,若快到期返回 true;解析不到则看 last_refresh 是否超过固定天数 → 返回布尔值。

调用关系:AuthManager::auth 在返回认证前会调用它决定是否刷新。

调用图:调用 1 个内部函数(parse_jwt_expiration);外部调用 3 个(now, days, minutes)。

AuthManager::refresh_external_auth2112–2164 ↗
async fn refresh_external_auth(
        &self,
        reason: ExternalAuthRefreshReason,
    ) -> Result<(), RefreshTokenError>

作用:请求外部认证提供者刷新凭证,并在 ChatGPT 场景下写入临时存储。它是外部托管认证的续票流程。

数据流:输入刷新原因 → 取 external_auth、工作区限制和旧账号 ID,构造上下文;调用 external_auth.refresh;API Key 模式只确认成功;ChatGPT 模式校验元数据和工作区,保存临时 AuthDotJson,再 reload → 返回结果。

调用关系:refresh_token_from_authority_impl 和 UnauthorizedRecovery 的 external_refresh 路径会调用它。

调用图:调用 6 个内部函数(default, auth_cached, external_auth, forced_chatgpt_workspace_id, reload, save_auth);被 1 处调用(refresh_token_from_authority_impl);外部调用 4 个(other, format!, Transient, from_external_tokens)。

AuthManager::refresh_and_persist_chatgpt_token2168–2185 ↗
async fn refresh_and_persist_chatgpt_token(
        &self,
        auth: &ChatgptAuth,
        refresh_token: String,
    ) -> Result<(), RefreshTokenError>

作用:刷新本地管理的 ChatGPT OAuth token,并把新 token 保存后重载缓存。

数据流:输入 ChatgptAuth 和 refresh token → 调用 request_chatgpt_token_refresh 向服务器换新 token → 调用 persist_tokens 写入存储 → 调用 reload 让内存看到新状态 → 返回成功或错误。

调用关系:refresh_token_from_authority_impl 在当前认证是普通 ChatGPT 登录时调用它。

调用图:调用 5 个内部函数(reload, client, storage, persist_tokens, request_chatgpt_token_refresh);被 1 处调用(refresh_token_from_authority_impl)。

login/src/auth/access_token.rs源码 ↗
domain_logicauth load / login handling

这里解决的是“同样是一串 token,系统该把它当成什么身份凭证”的问题。访问令牌可以理解成一张门票,但门票也分种类:一种是个人访问令牌,前缀固定是“at-”;另一种是代理身份用的 JWT,JWT 可以简单理解成带有身份信息的加密票据。这个文件先定义了一个小分类结果 CodexAccessToken:要么是 PersonalAccessToken,要么是 AgentIdentityJwt。真正的判断规则很直接:如果字符串以“at-”开头,就当作个人访问令牌;否则就当作代理身份 JWT。这样做的好处是,加载已有登录信息或用户输入新令牌时,不用到处重复写判断规则,避免不同地方判断不一致。

函数细节1
classify_codex_access_token8–14 ↗
fn classify_codex_access_token(access_token: &str) -> CodexAccessToken<'_>

作用:把传进来的一串访问令牌分成两类:以“at-”开头的是个人访问令牌,否则是代理身份 JWT。别人会用它来决定接下来走哪一种登录或认证处理方式。

数据流:进去的是一段 access_token 字符串。它读取固定前缀“at-”,检查这段字符串是不是以这个前缀开头;如果是,就把原字符串包成 PersonalAccessToken 分类结果;如果不是,就包成 AgentIdentityJwt 分类结果。出来的是一个分类后的 CodexAccessToken,不会修改原来的字符串。

调用关系:它位于认证流程的早期,像入口处的验票员。load_auth 在读取已有认证信息时会调用它,login_with_access_token 在用户用访问令牌登录时也会调用它;它自己只做分类,然后把结果交给后续流程按不同令牌类型继续处理。

调用图:被 2 处调用(load_auth, login_with_access_token);外部调用 2 个(AgentIdentityJwt, PersonalAccessToken)。

login/src/auth/personal_access_token.rs源码 ↗
domain_logiclogin/auth load

个人访问令牌可以理解成一把临时钥匙,但光有钥匙还不够,程序还要知道这把钥匙是谁的、能进哪个账号、账号有什么限制。这个文件做的事就是:拿到用户给的令牌后,调用认证服务的 whoami 接口,取回邮箱、用户 ID、账号 ID、套餐类型、是否 FedRAMP 账号等信息,然后封装成 PersonalAccessTokenAuth。它还特别注意安全:调试打印时不会把真正的 access token 打出来,而是显示成“已隐藏”。认证服务地址默认用生产地址,也可以用环境变量 CODEX_AUTHAPI_BASE_URL 改掉,方便测试或特殊部署。没有这层检查,程序可能只拿着一串令牌盲目工作,不知道它对应哪个账号,也无法做后续权限判断。

函数细节9
PersonalAccessTokenAuth::fmt30–35 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:这个函数定义 PersonalAccessTokenAuth 被打印成调试信息时长什么样。它的重点是保护令牌,不把真正的 access token 泄露到日志里。

数据流:进去的是一个 PersonalAccessTokenAuth 和一个用于写调试文本的输出器 → 它把结构名和账号元数据写进去,但把 access_token 固定写成“<redacted>” → 出来的是格式化结果,外部看到有用信息但看不到敏感令牌。

调用关系:当开发者或日志系统想打印这个登录对象时会走到这里。它把具体格式化工作交给标准的 debug_struct 工具,同时主动遮住最危险的字段。

调用图:外部调用 1 个(debug_struct)。

PersonalAccessTokenAuth::load39–46 ↗
async fn load(access_token: &str) -> std::io::Result<Self>

作用:这个函数是加载个人访问令牌的入口。它会决定该连哪个认证服务地址,然后用令牌去换取账号资料。

数据流:进去的是用户提供的 access_token → 它先读取环境变量 CODEX_AUTHAPI_BASE_URL,如果没有或为空就使用默认生产认证地址,再创建 HTTP 客户端 → 最后调用 hydrate_personal_access_token,成功时出来一个带完整账号信息的 PersonalAccessTokenAuth,失败时出来一个输入输出错误。

调用关系:from_personal_access_token、load_auth 和 login_with_access_token 会在需要用个人访问令牌登录时调用它。它自己不直接解析远端返回内容,而是把联网查询和组装对象的工作交给 hydrate_personal_access_token。

调用图:调用 2 个内部函数(create_client, hydrate_personal_access_token);被 3 处调用(from_personal_access_token, load_auth, login_with_access_token);外部调用 1 个(var)。

PersonalAccessTokenAuth::access_token48–50 ↗
fn access_token(&self) -> &str

作用:这个函数把保存下来的原始访问令牌取出来。需要真正向后端服务证明身份时,调用方会用到它。

数据流:进去的是已经加载好的 PersonalAccessTokenAuth → 它不修改任何东西,只返回内部保存的 access_token 的引用 → 出来的是那串访问令牌文本,仍然由原对象持有。

调用关系:它是一个简单取值口,供其他需要带上令牌发请求的代码使用。和加载流程相比,它不联网,也不做校验,只负责安全地借出已有数据。

PersonalAccessTokenAuth::account_id52–54 ↗
fn account_id(&self) -> &str

作用:这个函数返回这枚令牌所属的 ChatGPT 账号 ID。后续判断“这个账号能不能使用某个工作区”时会用到它。

数据流:进去的是已包含元数据的 PersonalAccessTokenAuth → 它从 metadata 里取出 chatgpt_account_id → 出来的是账号 ID 文本,不会改变对象内容。

调用关系:ensure_personal_access_token_workspace_allowed 会调用它来做工作区权限检查。也就是说,登录完成后,系统用这里提供的账号 ID 判断这把钥匙能不能开目标门。

调用图:被 1 处调用(ensure_personal_access_token_workspace_allowed)。

PersonalAccessTokenAuth::chatgpt_user_id56–58 ↗
fn chatgpt_user_id(&self) -> &str

作用:这个函数返回令牌对应的 ChatGPT 用户 ID。它用于区分“具体是哪一个用户”,而不是只知道账号。

数据流:进去的是 PersonalAccessTokenAuth → 它读取 metadata.chatgpt_user_id → 出来的是用户 ID 文本,对内部数据没有任何改动。

调用关系:它是登录身份对象对外提供的基础信息出口。其他模块需要记录、展示或传递用户身份时,可以从这里拿到用户 ID。

PersonalAccessTokenAuth::email60–62 ↗
fn email(&self) -> &str

作用:这个函数返回令牌所属用户的邮箱。邮箱通常用于展示给人看,让用户知道当前登录的是哪个账号。

数据流:进去的是已经水合好的认证对象,也就是已经查过远端资料的对象 → 它读取 metadata.email → 出来的是邮箱字符串的引用,不会复制或修改账号信息。

调用关系:它位于认证信息读取阶段。加载时远端返回邮箱,之后界面、日志或状态展示代码可以通过这个函数拿到它。

PersonalAccessTokenAuth::plan_type64–66 ↗
fn plan_type(&self) -> AccountPlanType

作用:这个函数把远端返回的套餐类型转换成系统内部更通用的账号套餐类型。这样别的地方不用关心远端原始字符串长什么样。

数据流:进去的是 PersonalAccessTokenAuth → 它读取 metadata.chatgpt_plan_type 这个原始套餐字符串,先用 InternalPlanType::from_raw_value 解释成内部认证套餐类型,再转换成账号层使用的 AccountPlanType → 出来的是一个规范化后的套餐枚举值。

调用关系:远端接口给的是原始文本,系统其他部分更喜欢明确的类型。这个函数就像翻译员,把认证接口的话翻译成账号模块能直接理解的话。

调用图:外部调用 1 个(from_raw_value)。

PersonalAccessTokenAuth::is_fedramp_account68–70 ↗
fn is_fedramp_account(&self) -> bool

作用:这个函数告诉调用方这个账号是不是 FedRAMP 账号。FedRAMP 可以简单理解为美国政府相关的云安全合规要求,通常会带来更严格的使用限制。

数据流:进去的是 PersonalAccessTokenAuth → 它读取 metadata.chatgpt_account_is_fedramp 这个布尔值 → 出来是真或假,不修改任何状态。

调用关系:它给后续策略判断提供一个清晰开关。其他代码如果需要按合规账号和普通账号走不同规则,就可以调用这个函数。

hydrate_personal_access_token73–108 ↗
async fn hydrate_personal_access_token(
    client: &CodexHttpClient,
    authapi_base_url: &str,
    access_token: &str,
) -> std::io::Result<PersonalAccessTokenAuth>

作用:这个函数真正拿令牌去认证服务查询“我是谁”。它负责发 HTTP 请求、检查请求是否成功、解析返回的账号资料,并组装成 PersonalAccessTokenAuth。

数据流:进去的是 HTTP 客户端、认证服务基础地址和 access_token → 它拼出 whoami 接口地址,用 bearer auth 方式把令牌放进请求头里发 GET 请求;如果网络失败、状态码不是成功、或者返回内容解析不了,就生成错误;如果一切正常,就把返回的 JSON 解析成 PersonalAccessTokenMetadata → 出来的是包含原始令牌和账号元数据的 PersonalAccessTokenAuth。

调用关系:PersonalAccessTokenAuth::load 会调用它完成最关键的远端确认工作。它把底层发请求交给 CodexHttpClient 的 get/send/json 等能力,把错误包装成标准输入输出错误,方便上层登录流程统一处理失败。

调用图:调用 1 个内部函数(get);被 1 处调用(load);外部调用 2 个(other, format!)。

login/src/token_data.rs源码 ↗
domain_logicauth load / token refresh / auth persistence

登录系统拿到的令牌里,最重要的是 JWT(JSON Web Token,一种用点号分成三段的登录凭证,里面带着一些用户信息)。这个文件做的事就像“拆快递”:外面是一串字符串,里面藏着邮箱、ChatGPT 套餐、用户编号、工作区编号、是否走 FedRAMP 等信息。TokenData 表示 auth.json 里保存的整包登录信息;IdTokenInfo 是从 id_token 里拆出来的“人能看懂的摘要”。文件还保留原始 JWT,因为写回文件时仍要写原串,而不是写摘要。解析时,它只解开 JWT 的中间 payload 部分,不做签名验真;主要用途是读取客户端已经拿到的身份信息。它也能读 exp 字段,也就是过期时间,方便别的地方提前刷新令牌。

函数细节9
IdTokenInfo::get_chatgpt_plan_type45–50 ↗
fn get_chatgpt_plan_type(&self) -> Option<String>

作用:把用户的 ChatGPT 套餐类型变成适合展示给人的文字。比如后端给的是已知套餐,就用友好的显示名;如果是不认识的新值,就原样返回,避免信息丢失。

数据流:进去的是一个已经解析好的 IdTokenInfo;它查看里面的 chatgpt_plan_type;如果有值,就把已知套餐转成显示名,把未知套餐原样复制出来;出来的是一个可选字符串,没有套餐信息时就是空。

调用关系:这是 IdTokenInfo 上的便捷读法,供界面、日志或状态展示使用。它不再解析 JWT,只消费 parse_chatgpt_jwt_claims 之前整理好的套餐字段。

IdTokenInfo::get_chatgpt_plan_type_raw52–57 ↗
fn get_chatgpt_plan_type_raw(&self) -> Option<String>

作用:把用户套餐类型变成后端原始使用的字符串。它适合需要精确值的地方,而不是给人看的漂亮名称。

数据流:进去的是 IdTokenInfo;它读取 chatgpt_plan_type;已知套餐会取它的原始值,未知套餐会保留原字符串;出来的是可选字符串,没有套餐时返回空。

调用关系:它和 get_chatgpt_plan_type 很像,但用途不同:一个偏展示,一个偏保留后端原值。它依赖前面解析出的 PlanType,不自己拆令牌。

IdTokenInfo::is_workspace_account59–64 ↗
fn is_workspace_account(&self) -> bool

作用:判断当前令牌代表的账号是不是工作区类账号,比如企业、团队或类似组织账号。这样别的代码可以按账号类型走不同流程。

数据流:进去的是 IdTokenInfo;它检查 chatgpt_plan_type 是否是已知套餐,并询问这个套餐是否属于工作区账号;出来的是 true 或 false。

调用关系:这是账号分类的小判断。它使用语言里的 matches! 模式匹配工具来做检查,通常被需要区分个人账号和组织账号的流程调用。

调用图:外部调用 1 个(matches!)。

IdTokenInfo::is_fedramp_account66–68 ↗
fn is_fedramp_account(&self) -> bool

作用:判断当前选择的 ChatGPT 工作区是否要求走 FedRAMP 边缘服务。FedRAMP 可以粗略理解为美国政府相关的合规通道,网络路由可能不同。

数据流:进去的是 IdTokenInfo;它直接读取 chatgpt_account_is_fedramp 这个布尔值;出来的是 true 或 false,不做额外计算。

调用关系:这个函数是一个清晰的入口,让其他代码不用直接碰字段名。字段本身来自 parse_chatgpt_jwt_claims 对 JWT 内容的解析。

decode_jwt_payload117–128 ↗
fn decode_jwt_payload(jwt: &str) -> Result<T, IdTokenInfoError>

作用:把 JWT 中间那段 payload 解开,并按调用者指定的结构读成数据。它是本文件所有 JWT 解析工作的底层工具。

数据流:进去的是一整串 JWT 字符串;它先按点号切成 header、payload、signature 三段,格式不对就报错;再用 URL 安全的 Base64 解码 payload;最后把解出来的 JSON 字节交给 JSON 解析器,变成调用者想要的结构;出来的是解析好的数据或错误。

调用关系:parse_jwt_expiration 和 parse_chatgpt_jwt_claims 都把真正的拆包工作交给它。它调用 serde_json 的 from_slice 来把 JSON 字节变成 Rust 数据结构,是这个文件里的基础零件。

调用图:被 2 处调用(parse_chatgpt_jwt_claims, parse_jwt_expiration);外部调用 1 个(from_slice)。

parse_jwt_expiration130–135 ↗
fn parse_jwt_expiration(jwt: &str) -> Result<Option<DateTime<Utc>>, IdTokenInfoError>

作用:从 JWT 里读取过期时间 exp,并把它变成程序好处理的 UTC 时间。这样系统能知道令牌是不是快过期了。

数据流:进去的是 JWT 字符串;它调用 decode_jwt_payload 只解析标准过期字段 exp;如果 exp 存在,就把秒级时间戳转成 UTC 时间;如果没有 exp 或时间戳无效,就返回空;格式或 JSON 出错时返回错误。

调用关系:它被 should_refresh_proactively 使用,用来判断是否需要提前刷新令牌。它不关心用户是谁,只关心这张“通行证”什么时候失效。

调用图:调用 1 个内部函数(decode_jwt_payload);被 1 处调用(should_refresh_proactively)。

parse_chatgpt_jwt_claims137–161 ↗
fn parse_chatgpt_jwt_claims(jwt: &str) -> Result<IdTokenInfo, IdTokenInfoError>

作用:从 ChatGPT 的 id_token 里提取用户和账号相关信息,整理成 IdTokenInfo。它把一串机器用的令牌变成程序其他部分容易使用的摘要。

数据流:进去的是 JWT 字符串;它先用 decode_jwt_payload 解出声明内容;然后优先取顶层邮箱,如果没有就取 profile 里的邮箱;再读取 auth 区域里的套餐、用户 ID、账号 ID、FedRAMP 标记;最后把这些字段连同原始 JWT 一起放进 IdTokenInfo。没有 auth 区域时,也会返回一个只带邮箱和原始 JWT 的结果,而不是直接失败。

调用关系:这是本文件最核心的解析函数。写入、持久化、从外部令牌创建登录状态、测试造数据,以及 deserialize_id_token 读 auth.json 时都会用它。它把底层的 decode_jwt_payload 包装成面向 ChatGPT 登录信息的结果。

调用图:调用 1 个内部函数(decode_jwt_payload);被 9 处调用(remote_control_auth_dot_json, remote_control_auth_dot_json, write_chatgpt_auth, from_external_tokens, persist_tokens, id_token_with_prefix, deserialize_id_token, chatgpt_auth_tokens_for_tests, write_chatgpt_auth)。

deserialize_id_token163–169 ↗
fn deserialize_id_token(deserializer: D) -> Result<IdTokenInfo, D::Error>

作用:在读取保存的登录文件时,把文件里的 id_token 字符串自动变成 IdTokenInfo。这样外面的代码读到的不是一串原文,而是已经拆好的信息。

数据流:进去的是 serde 反序列化器,也就是读取 JSON 字段的工具;它先把字段读成字符串;再调用 parse_chatgpt_jwt_claims 解析 JWT;出来的是 IdTokenInfo。解析失败时,它把错误包装成 serde 能理解的读取错误。

调用关系:TokenData 的 id_token 字段指定用它来读取。也就是说,只要有人反序列化 TokenData,它就会自动参与,把 auth.json 里的 id_token 拆成结构化信息。

调用图:调用 1 个内部函数(parse_chatgpt_jwt_claims);外部调用 1 个(deserialize)。

serialize_id_token171–176 ↗
fn serialize_id_token(id_token: &IdTokenInfo, serializer: S) -> Result<S::Ok, S::Error>

作用:在保存登录文件时,把 IdTokenInfo 写回成原始 id_token 字符串。这样文件格式保持不变,不会把解析后的摘要错误地写进 auth.json。

数据流:进去的是 IdTokenInfo 和 serde 序列化器,也就是写 JSON 字段的工具;它取出 raw_jwt;然后把这串原始 JWT 作为字符串写出;出来的是序列化成功结果或写出错误。

调用关系:TokenData 的 id_token 字段指定用它来写出。它和 deserialize_id_token 正好一读一写:读取时拆开看,保存时仍放回原始令牌。

调用图:外部调用 1 个(serialize_str)。

安装身份

此文件建立稳定的每安装标识符,用于在多次运行中识别本地 Codex 实例。

core/src/installation_id.rs源码 ↗
domain_logicstartup / identity resolution

这个文件解决的是“本地安装身份”问题。它会在 Codex 的 home 目录里放一个叫 installation_id 的小文件,里面是一串 UUID(通用唯一标识符,可以理解成很难撞号的随机身份证号)。程序要用这个编号时,会先确保目录存在,再打开这个文件,并加文件锁(像给文件上锁,避免两个程序同时写坏它)。如果文件里已经有合法 UUID,就直接复用;如果文件不存在、为空,或者内容坏了,就生成一个新的 UUID 写回去。它还特别注意 Unix 系统上的文件权限,把权限设成 0644,也就是所有人可读、只有拥有者可写。因为这里用了普通磁盘读写,函数会把这段阻塞操作放到后台线程里,避免卡住异步运行环境。

函数细节4
resolve_installation_id19–64 ↗
async fn resolve_installation_id(codex_home: &AbsolutePathBuf) -> Result<String>

作用:找到或创建当前安装的唯一编号。别人调用它时,只需要给出 Codex 的 home 目录,它就会保证返回一个可用、稳定、已经保存到磁盘的 UUID。

数据流:输入是 codex_home,也就是 Codex 用来放本地数据的目录路径。它先把目录和 installation_id 文件名拼起来,再确保目录存在;然后在后台线程里打开或创建这个文件,给文件上锁,读取里面的文字。如果文字是合法 UUID,就整理成标准小写格式返回;如果没有合法内容,就生成新 UUID,清空文件,写入新值,刷新并同步到磁盘。输出是一串安装编号,同时磁盘上的 installation_id 文件也会被创建或修正。

调用关系:这是这个文件的核心函数。三个测试函数都会调用它,分别检查首次生成、复用旧编号、修复坏文件这三种情况。它自己会调用路径拼接 join、异步建目录 create_dir_all,并用 spawn_blocking 把可能卡住的文件读写交给后台线程处理。

调用图:调用 1 个内部函数(join);被 3 处调用(resolve_installation_id_generates_and_persists_uuid, resolve_installation_id_reuses_existing_uuid, resolve_installation_id_rewrites_invalid_file_contents);外部调用 2 个(create_dir_all, spawn_blocking)。

tests::resolve_installation_id_generates_and_persists_uuid79–103 ↗
async fn resolve_installation_id_generates_and_persists_uuid()

作用:测试第一次没有安装编号时,函数会生成一个新的 UUID,并真的写到 installation_id 文件里。它还检查 Unix 系统下文件权限是不是被设成预期的 0644。

数据流:它先创建一个临时目录,当作假的 Codex home。然后调用 resolve_installation_id,拿到返回的安装编号。接着读取磁盘上的 installation_id 文件,确认文件内容和返回值一样,并确认这串文字能被解析成 UUID。在 Unix 系统上,它还读取文件元数据,确认权限是 0644。

调用关系:这是验证“首次启动”场景的测试。它直接调用核心函数 resolve_installation_id,然后用断言检查结果。它还会用临时目录创建函数、UUID 解析、文件 metadata 读取这些外部工具来完成验证。

调用图:调用 1 个内部函数(resolve_installation_id);外部调用 4 个(new, assert!, assert_eq!, metadata)。

tests::resolve_installation_id_reuses_existing_uuid106–126 ↗
async fn resolve_installation_id_reuses_existing_uuid()

作用:测试如果 installation_id 文件里已经有合法 UUID,函数不会随便换一个新编号,而是继续使用原来的编号。这样同一次安装的身份才稳定。

数据流:它先创建临时目录,再生成一个 UUID,并故意写成大写形式放进 installation_id 文件。然后调用 resolve_installation_id。函数读到已有 UUID 后会解析并标准化,所以测试最后确认返回值等于这个 UUID 的标准字符串形式,而不是另起炉灶生成新值。

调用关系:这是验证“已有安装编号”场景的测试。它先用普通文件写入准备测试现场,再调用 resolve_installation_id,最后用 assert_eq 检查核心函数是否按规则复用了旧编号。

调用图:调用 1 个内部函数(resolve_installation_id);外部调用 4 个(new, new_v4, assert_eq!, write)。

tests::resolve_installation_id_rewrites_invalid_file_contents129–148 ↗
async fn resolve_installation_id_rewrites_invalid_file_contents()

作用:测试如果 installation_id 文件被写坏了,比如内容不是 UUID,函数会自动修复:生成一个新的合法编号并覆盖坏内容。

数据流:它先创建临时目录,并往 installation_id 文件里写入 not-a-uuid 这样的无效文字。然后调用 resolve_installation_id。函数发现内容不能当作 UUID,就生成新 UUID,清空原文件并写入新值。测试最后确认返回值是合法 UUID,并确认磁盘文件内容已经变成这个新返回值。

调用关系:这是验证“文件损坏后自愈”场景的测试。它通过 write 人为制造坏文件,再调用 resolve_installation_id,看核心函数是否能把坏状态变回可用状态。

调用图:调用 1 个内部函数(resolve_installation_id);外部调用 4 个(new, assert!, assert_eq!, write)。

Windows 沙箱账户

此文件管理沙箱账户就绪所需的 Windows 专用本地用户、组、凭据和标记。

windows-sandbox-rs/src/bin/setup_main/win/sandbox_users.rs源码 ↗
domain_logicsetup

这个文件做的是“给沙箱办户口”。它会创建一个叫 CodexSandboxUsers 的本地用户组,再创建离线和在线两个沙箱用户,把它们放进这个组里,并生成随机密码。密码不会明文乱放,而是先用 DPAPI(Windows 自带的数据保护功能,可以把敏感数据加密保存)保护,再写进 sandbox_users.json。它还会处理 SID(Windows 里用来唯一识别用户或组的一串身份号),因为权限设置真正认的是 SID,不只是名字。最后,它会先创建一个受保护的 setup_marker.json 空文件,等所有设置成功后再写入完整内容。这个标记文件像“验收单”:空文件表示还没装好,写完整才表示沙箱准备就绪。

函数细节16
ensure_sandbox_users_group62–64 ↗
fn ensure_sandbox_users_group(log: &mut dyn Write) -> Result<()>

作用:确保沙箱专用用户组存在。这个组像一个名单,后面所有沙箱用户都放进去,方便统一给权限或限制权限。

数据流:输入是日志输出口 → 它使用固定的组名和说明文字,调用底层的本地组创建函数 → 如果组已经存在就当作成功;如果创建失败,会把错误写进日志并返回失败。

调用关系:它是 provision_sandbox_users 的第一步。真正碰 Windows 本地组的是 ensure_local_group,这个函数只是把“沙箱用户组”这个固定目标交给它。

调用图:调用 1 个内部函数(ensure_local_group);被 1 处调用(provision_sandbox_users)。

resolve_sandbox_users_group_sid66–68 ↗
fn resolve_sandbox_users_group_sid() -> Result<Vec<u8>>

作用:查出沙箱用户组对应的 SID。SID 是 Windows 真正用于权限判断的身份编号,比组名更可靠。

数据流:输入为空 → 它拿固定组名 CodexSandboxUsers 去查询 SID → 输出这组 SID 的原始字节,供后续权限设置使用。

调用关系:run_provision_only、run_read_acl_only 和 run_setup_full 在需要给这个组设置或读取权限时会调用它。它把实际查询工作交给 resolve_sid。

调用图:调用 1 个内部函数(resolve_sid);被 3 处调用(run_provision_only, run_read_acl_only, run_setup_full)。

provision_sandbox_users70–93 ↗
fn provision_sandbox_users(
    codex_home: &Path,
    offline_username: &str,
    online_username: &str,
    log: &mut dyn Write,
) -> Result<()>

作用:一次性准备好沙箱需要的两个用户:一个离线用户,一个在线用户。它还会生成密码并保存到秘密文件里,方便之后启动沙箱时使用。

数据流:输入是 Codex 主目录、两个用户名和日志输出口 → 它先确保沙箱用户组存在,再生成两份随机密码,然后分别创建或更新两个本地用户,并把用户名和加密后的密码写入 secrets 目录 → 成功后系统就有可用的沙箱账号和对应的凭据文件。

调用关系:它由 provision_and_hide_sandbox_users 调用,是用户准备流程的核心。它依次调用 ensure_sandbox_users_group、random_password、ensure_sandbox_user 和 write_secrets,把“建组、建用户、存密码”串成一条完整流水线。

调用图:调用 4 个内部函数(ensure_sandbox_user, ensure_sandbox_users_group, random_password, write_secrets);被 1 处调用(provision_and_hide_sandbox_users);外部调用 2 个(format!, log_line)。

ensure_sandbox_user95–99 ↗
fn ensure_sandbox_user(username: &str, password: &str, log: &mut dyn Write) -> Result<()>

作用:确保某一个沙箱用户存在,并且加入沙箱专用用户组。它把“有这个账号”和“账号属于正确名单”两个条件都补齐。

数据流:输入是用户名、密码和日志输出口 → 它先创建或更新本地用户,再把这个用户加入 CodexSandboxUsers 组 → 输出成功或失败,并可能改变 Windows 本地账号和组成员关系。

调用关系:provision_sandbox_users 会对离线用户和在线用户各调用一次它。它内部把创建账号交给 ensure_local_user,把加入组交给 ensure_local_group_member。

调用图:调用 2 个内部函数(ensure_local_group_member, ensure_local_user);被 1 处调用(provision_sandbox_users)。

ensure_local_user101–163 ↗
fn ensure_local_user(name: &str, password: &str, log: &mut dyn Write) -> Result<()>

作用:在 Windows 本机创建一个普通用户;如果用户已经存在,就更新它的密码。这样重复运行安装程序也不会因为账号已存在而直接坏掉。

数据流:输入是用户名、密码和日志输出口 → 它把字符串转成 Windows API 需要的宽字符格式,调用 NetUserAdd 创建用户;如果创建不成功,就尝试用 NetUserSetInfo 改密码;之后尽量把它加入 Windows 的普通 Users 组 → 输出成功或带有明确错误码的失败,并改动本地用户数据库。

调用关系:它由 ensure_sandbox_user 调用,是实际操作 Windows 用户账户的地方。为了找到本地化系统里的 Users 组名字,它会调用 lookup_account_name_for_sid,因为不同语言的 Windows 上组名可能不一样。

调用图:调用 2 个内部函数(lookup_account_name_for_sid, new);被 1 处调用(ensure_sandbox_user);外部调用 10 个(new, new, to_wide, format!, null, null_mut, log_line, NetLocalGroupAddMembers, NetUserAdd, NetUserSetInfo)。

ensure_local_group165–195 ↗
fn ensure_local_group(name: &str, comment: &str, log: &mut dyn Write) -> Result<()>

作用:创建一个 Windows 本地用户组。它允许“组已经存在”这种情况,因为安装程序可能被重复运行。

数据流:输入是组名、组说明和日志输出口 → 它把名字和说明转换成 Windows API 格式,调用 NetLocalGroupAdd 创建本地组 → 如果成功或组已存在,返回成功;如果是别的错误,写日志并返回带安装错误码的失败。

调用关系:ensure_sandbox_users_group 调用它来创建 CodexSandboxUsers。它是沙箱用户组创建这一步真正接触 Windows 系统接口的函数。

调用图:调用 1 个内部函数(new);被 1 处调用(ensure_sandbox_users_group);外部调用 7 个(new, new, to_wide, format!, null, log_line, NetLocalGroupAdd)。

ensure_local_group_member197–215 ↗
fn ensure_local_group_member(group_name: &str, member_name: &str) -> Result<()>

作用:把某个用户加入某个本地组。它不在乎用户是否已经在组里,因为目标只是保证最后属于这个组。

数据流:输入是组名和成员名 → 它把两者转成 Windows API 格式,调用 NetLocalGroupAddMembers 添加成员 → 不管“已存在”这类添加错误,最后返回成功。

调用关系:ensure_sandbox_user 调用它,把新建或已存在的沙箱用户放进 CodexSandboxUsers 组。它是用户和组之间“挂钩”的那一步。

调用图:被 1 处调用(ensure_sandbox_user);外部调用 4 个(new, to_wide, null, NetLocalGroupAddMembers)。

resolve_sid217–253 ↗
fn resolve_sid(name: &str) -> Result<Vec<u8>>

作用:把用户或组的名字转换成 SID。权限系统最终看的是 SID,所以很多安全相关步骤都要先做这个转换。

数据流:输入是一个账号名或组名 → 如果是常见内置名字,比如 Administrators、Users、Everyone,就直接用内置 SID 字符串转换;否则调用 Windows 的 LookupAccountNameW 查询,并在缓冲区不够时自动扩大空间重试 → 输出 SID 的字节形式,或者返回查询失败信息。

调用关系:它被 lock_sandbox_dir、run_provision_only、run_read_acl_only、run_setup_full、prepare_setup_marker 和 resolve_sandbox_users_group_sid 使用。它自己会先问 well_known_sid_str,必要时再调用 sid_bytes_from_string 或 Windows 查询接口。

调用图:调用 2 个内部函数(sid_bytes_from_string, well_known_sid_str);被 6 处调用(lock_sandbox_dir, run_provision_only, run_read_acl_only, run_setup_full, prepare_setup_marker, resolve_sandbox_users_group_sid);外部调用 8 个(new, new, anyhow!, to_wide, null, vec!, GetLastError, LookupAccountNameW)。

well_known_sid_str255–264 ↗
fn well_known_sid_str(name: &str) -> Option<&'static str>

作用:识别一些 Windows 固定内置身份,并给出它们的标准 SID 字符串。这样就不用依赖当前系统语言里的组名写法。

数据流:输入是一个名字 → 它检查是不是 Administrators、Users、Authenticated Users、Everyone 或 SYSTEM → 如果匹配,输出对应 SID 字符串;否则输出空。

调用关系:resolve_sid 会先调用它。它像一本小字典,能让常见系统身份走更稳定、更直接的路线。

调用图:被 1 处调用(resolve_sid)。

sid_bytes_from_string266–291 ↗
fn sid_bytes_from_string(sid_str: &str) -> Result<Vec<u8>>

作用:把 SID 的文本写法转换成 Windows 可用的原始字节。文本 SID 适合人看,字节 SID 适合系统权限接口使用。

数据流:输入是类似 S-1-5-32-544 的 SID 字符串 → 它调用 ConvertStringSidToSidW 让 Windows 解析,再读取长度并复制出一份字节数据,最后释放 Windows 分配的内存 → 输出 SID 字节,或在转换、取长度、复制失败时返回错误。

调用关系:resolve_sid 在处理内置身份时会调用它。它负责把 well_known_sid_str 给出的固定字符串变成后续代码能传递和保存的二进制形式。

调用图:被 1 处调用(resolve_sid);外部调用 9 个(new, anyhow!, to_wide, null_mut, vec!, LocalFree, ConvertStringSidToSidW, CopySid, GetLengthSid)。

lookup_account_name_for_sid293–351 ↗
fn lookup_account_name_for_sid(sid_str: &str) -> Result<String>

作用:把一个 SID 字符串反查成本机上的账号或组名。这样在不同语言的 Windows 上,也能找到真正的 Users 组名称。

数据流:输入是 SID 字符串 → 它先把字符串 SID 转成 Windows SID 指针,再调用 LookupAccountSidW 先询问需要多大缓冲区,然后分配空间再次查询 → 输出本机显示的账号名,并清理末尾的空字符;失败时返回错误。

调用关系:ensure_local_user 调用它来找到普通 Users 组的真实名字。这个步骤是为了把沙箱用户也放进 Windows 的普通用户集合里,但如果查不到,只记录日志并跳过。

调用图:被 1 处调用(ensure_local_user);外部调用 11 个(new, from_utf16_lossy, anyhow!, to_wide, null, null_mut, vec!, GetLastError, LocalFree, ConvertStringSidToSidW (+1 more))。

sid_bytes_to_psid353–364 ↗
fn sid_bytes_to_psid(sid: &[u8]) -> Result<*mut c_void>

作用:把保存着的 SID 字节重新变成 Windows API 需要的 SID 指针。PSID 可以理解成 Windows 接口直接吃的“身份地址”。

数据流:输入是一段 SID 字节 → 它先用 string_from_sid_bytes 转回文本 SID,再调用 ConvertStringSidToSidW 生成 Windows 分配的 SID 指针 → 输出这个指针;如果格式不对或转换失败,返回错误。

调用关系:run_read_acl_only 和 run_setup_full 在需要把 SID 交给 Windows 权限接口时会调用它。它连接了项目内部的 SID 保存格式和 Windows 原生接口格式。

调用图:被 2 处调用(run_read_acl_only, run_setup_full);外部调用 6 个(new, anyhow!, string_from_sid_bytes, to_wide, null_mut, ConvertStringSidToSidW)。

random_password366–378 ↗
fn random_password() -> String

作用:生成一个新的随机密码,给沙箱本地用户使用。随机密码可以避免固定默认密码带来的安全风险。

数据流:输入为空 → 它从系统随机源初始化随机数生成器,取 24 个随机字节,并把每个字节映射到大小写字母、数字和符号组成的字符表 → 输出一个 24 位密码字符串。

调用关系:provision_sandbox_users 会调用它两次,分别给离线用户和在线用户生成密码。生成后的密码会传给 ensure_sandbox_user 创建账号,并交给 write_secrets 加密保存。

调用图:被 1 处调用(provision_sandbox_users);外部调用 1 个(from_entropy)。

write_secrets405–462 ↗
fn write_secrets(
    codex_home: &Path,
    offline_user: &str,
    offline_pwd: &str,
    online_user: &str,
    online_pwd: &str,
) -> Result<()>

作用:把两个沙箱用户的用户名和密码写入秘密文件,但密码会先加密再保存。这个文件是以后启动或使用沙箱账号时的凭据来源。

数据流:输入是 Codex 主目录、两个用户名和两份明文密码 → 它创建 secrets 目录,用 dpapi_protect 保护两份密码,再用 Base64(把二进制数据变成文本的编码方式,不是加密)转成可写入 JSON 的字符串,最后生成 sandbox_users.json → 输出成功或写文件、加密、序列化失败。

调用关系:provision_sandbox_users 在两个用户都准备好后调用它。它依赖 sandbox_secrets_dir 找到秘密目录,依赖 dpapi_protect 做密码保护,依赖 serde_json 生成可读的 JSON 文件。

调用图:被 1 处调用(provision_sandbox_users);外部调用 5 个(dpapi_protect, sandbox_secrets_dir, to_vec_pretty, create_dir_all, write)。

prepare_setup_marker468–549 ↗
fn prepare_setup_marker(codex_home: &Path, real_user: &str) -> Result<()>

作用:在正式设置开始前,先创建一个受保护的 setup_marker.json 空文件。这个空文件故意不能通过“已准备好”检查,用来防止安装中途失败时被误认为已经完成。

数据流:输入是 Codex 主目录和真实用户名称 → 它先删除旧标记文件,再查出真实用户的 SID,生成一份权限说明:只有 SYSTEM、管理员和真实用户能完全访问;然后用 Windows CreateFileW 按这个权限创建空标记文件 → 输出成功,或在删除、查 SID、创建权限描述、建文件失败时返回安装错误。

调用关系:run_setup 在开始完整安装时调用它。它会调用 resolve_sid 找真实用户身份,调用 sandbox_dir 找标记文件位置,并直接使用 Windows 安全描述符和文件创建接口来提前锁好权限。

调用图:调用 2 个内部函数(resolve_sid, new);被 1 处调用(run_setup);外部调用 11 个(new, sandbox_dir, to_wide, format!, remove_file, null_mut, CloseHandle, GetLastError, LocalFree, ConvertStringSecurityDescriptorToSecurityDescriptorW (+1 more))。

commit_setup_marker551–585 ↗
fn commit_setup_marker(
    codex_home: &Path,
    offline_user: &str,
    online_user: &str,
    proxy_ports: &[u16],
    allow_local_binding: bool,
) -> Result<()>

作用:在所有设置都成功后,把 setup_marker.json 写成有效内容,表示沙箱已经准备完成。它像最后盖章,前面的步骤没成功就不会盖。

数据流:输入是 Codex 主目录、离线和在线用户名、代理端口列表,以及是否允许本地绑定的开关 → 它组装包含版本号、用户名、创建时间、端口和权限根目录列表的 JSON 内容,并写到之前创建好的标记文件里 → 输出成功或序列化、写文件失败。

调用关系:run_setup 在安装流程最后调用它。它不重新设置文件权限,而是沿用 prepare_setup_marker 先前创建文件时设置好的保护权限,只负责把“安装完成”的内容写进去。

调用图:被 1 处调用(run_setup);外部调用 5 个(new, now, sandbox_dir, to_vec_pretty, write)。