Codex 系统手册

提供商和后端 auth 适配

stage-5.219 个文件

这一阶段像给全系统做一套“通行证柜台”,属于幕后支撑。模型提供方先统一入口,让 OpenAI 类服务、Bedrock 等都按同一套说法接入。Bearer 令牌、外部取令牌、AWS 配置和 SigV4 签名,负责把不同钥匙正确盖到请求上。代理身份负责给本地代理办身份证、登记任务。API、远程控制、MCP 和限额重置这些客户端,再用这些适配器检查该怎么登录、该带什么头,避免拿错钥匙或漏带凭证。

本阶段的文件19

提供方运行时接口

定义公共模型提供方 API,以及用于选择并配置通用提供方和 Bedrock 支持提供方的主要运行时连接。

model-provider/src/lib.rs源码 ↗
othercross-cutting

可以把这个文件理解成一家商店的前台。库里面其实分了好几个区域:有处理认证的,有处理 Bearer Token(一种常见的登录令牌)的,有对接 Amazon Bedrock 的,有描述模型提供方能力和状态的。但外部使用者不需要知道这些东西分别放在哪个房间,只要从这里导入就行。它通过 mod 声明内部模块,再用 pub use 把常用东西重新导出,比如创建模型提供方的 create_model_provider、表示模型提供方的 ModelProvider、认证相关的 BearerAuthProvider,以及账号状态和错误类型。没有它也能写代码,但使用者就得知道内部文件结构,导入路径会更乱,也更容易因为内部重构而坏掉。这个文件本身不做计算,更像是稳定的公共接口清单。

model-provider/src/provider.rs源码 ↗
domain_logiccross-cutting;启动配置服务商时、发请求前准备认证时、查看账号状态和刷新模型列表时都会用到

Codex 要调用不同的模型后端,但每家服务商的地址、认证方式、模型列表和支持能力都不一样。这个文件就像一个“统一插座”:外面只认 ModelProvider 这套接口,里面再按服务商类型接到不同实现。默认实现 ConfiguredModelProvider 使用配置里的 ModelProviderInfo,并按需要拿 AuthManager(认证管理器,也就是保存和刷新登录凭证的工具)来取 token。它还能告诉应用:当前账号是不是 API Key、是不是 ChatGPT 登录、是否缺少账号信息,以及这个服务商是否支持网页搜索、图片生成等能力。创建入口 create_model_provider 会先判断是不是 Amazon Bedrock;是的话走专门实现,不是的话走通用 OpenAI 兼容实现。模型列表也分两种:配置里直接给了就用静态列表;没给就去远端 /models 接口拉取。

函数细节38
ProviderCapabilities::default36–42 ↗
fn default() -> Self

作用:给服务商能力设置默认值:默认认为命名空间工具、图片生成、网页搜索都可以用。这样普通服务商不用每个都手写一遍。

数据流:进去没有额外输入 → 它直接生成一份 ProviderCapabilities → 出来的是三个能力开关都为 true 的默认能力说明。

调用关系:ModelProvider::capabilities 会调用它,作为所有没有特别声明能力限制的服务商的默认答案。

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

ProviderAccountError::fmt60–75 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:把账号状态错误变成人能看懂的文字。比如缺少 ChatGPT 邮箱和套餐信息,或者把 Bedrock API Key 用在了不支持的服务商上。

数据流:进去的是一个具体错误类型和一个文本输出位置 → 它按错误种类选择一句说明文字并写进去 → 出来的是格式化结果,供日志、界面或错误链显示。

调用关系:它实现的是 Rust 的 Display 显示接口,内部把活交给 write! 写文本;当 ProviderAccountError 被打印或展示时会自动用到。

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

ModelProvider::capabilities104–106 ↗
fn capabilities(&self) -> ProviderCapabilities

作用:告诉外界当前服务商最多支持哪些功能。默认答案是“都支持”,特殊服务商可以改写这个答案。

数据流:进去的是当前服务商对象 → 默认调用 ProviderCapabilities::default → 出来的是一份能力开关表。

调用关系:这是 ModelProvider 接口的一部分;默认实现调用 ProviderCapabilities::default,具体服务商如果有限制,可以覆盖它。

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

ModelProvider::approval_review_preferred_model111–113 ↗
fn approval_review_preferred_model(&self) -> &'static str

作用:告诉系统做“自动审批复查”时优先用哪个模型。默认用通用模型名 codex-auto-review。

数据流:进去的是当前服务商对象 → 它不查配置,直接返回默认模型名 → 出来的是一个静态字符串模型 ID。

调用关系:这是接口默认行为;需要后端专属模型 ID 的服务商可以自己覆盖。

ModelProvider::memory_extraction_preferred_model118–120 ↗
fn memory_extraction_preferred_model(&self) -> &'static str

作用:告诉系统做“记忆提取”时优先用哪个模型。记忆提取就是从对话里整理出值得保存的信息。

数据流:进去的是当前服务商对象 → 它直接返回默认模型名 gpt-5.4-mini → 出来的是一个模型 ID。

调用关系:这是 ModelProvider 的默认方法;没有特别要求的服务商沿用它,特殊服务商可以覆盖。

ModelProvider::memory_consolidation_preferred_model125–127 ↗
fn memory_consolidation_preferred_model(&self) -> &'static str

作用:告诉系统做“记忆合并整理”时优先用哪个模型。它用于把零散记忆归并成更稳定的内容。

数据流:进去的是当前服务商对象 → 它直接返回默认模型名 gpt-5.4 → 出来的是一个模型 ID。

调用关系:这是接口默认行为;具体服务商如果模型命名不同,可以自己实现另一套返回值。

ModelProvider::supports_attestation130–132 ↗
fn supports_attestation(&self) -> bool

作用:告诉系统这个服务商的请求是否支持 attestation(可理解为给请求附上一种可信身份证明)。默认不支持。

数据流:进去的是当前服务商对象 → 默认不检查任何凭证 → 出来是 false。

调用关系:这是 ModelProvider 的默认方法;ConfiguredModelProvider 会覆盖它,根据是否是 ChatGPT 登录来判断。

ModelProvider::api_provider149–155 ↗
fn api_provider(&self) -> ModelProviderFuture<'_, codex_protocol::error::Result<Provider>>

作用:把当前模型服务商配置转换成 API 客户端能直接使用的 Provider 配置。它会顺便看一下当前认证方式。

数据流:进去的是服务商对象 → 它异步读取 auth,也就是当前登录凭证;再把凭证的认证模式交给 ModelProviderInfo 转换 → 出来的是 API 客户端用的 Provider,或一个错误。

调用关系:这是接口默认方法;它用 Box::pin 包住异步工作,调用 self.auth() 和 self.info().to_api_provider(...) 完成转换。

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

ModelProvider::runtime_base_url158–162 ↗
fn runtime_base_url(
        &self,
    ) -> ModelProviderFuture<'_, codex_protocol::error::Result<Option<String>>>

作用:告诉请求真正要打到哪个基础网址。默认就是配置里的 base_url。

数据流:进去的是服务商对象 → 它读取 self.info().base_url → 出来的是可能存在的 URL 字符串,外层带 Result 表示也可能失败。

调用关系:这是接口默认方法;图里显示它用 Box::pin 包装成异步结果,特殊服务商可以覆盖成运行时动态地址。

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

ModelProvider::api_auth165–172 ↗
fn api_auth(
        &self,
    ) -> ModelProviderFuture<'_, codex_protocol::error::Result<SharedAuthProvider>>

作用:为 API 请求准备认证提供者,也就是之后给 HTTP 请求加密钥或 token 的那部分工具。

数据流:进去的是服务商对象 → 它先异步拿当前 auth,再结合服务商配置调用 resolve_provider_auth → 出来的是 SharedAuthProvider,供真正发请求时附加认证信息。

调用关系:这是接口默认方法;它把认证判断交给 resolve_provider_auth,自己负责把当前服务商和当前凭证串起来。

调用图:外部调用 2 个(pin, resolve_provider_auth)。

create_model_provider188–197 ↗
fn create_model_provider(
    provider_info: ModelProviderInfo,
    auth_manager: Option<Arc<AuthManager>>,
) -> SharedModelProvider

作用:根据配置创建一个真正可用的模型服务商对象。调用方不用自己判断该用 Bedrock 实现还是通用实现。

数据流:进去的是 ModelProviderInfo 配置和可选 AuthManager → 它检查配置是否是 Amazon Bedrock;是就创建 AmazonBedrockModelProvider,不是就创建 ConfiguredModelProvider → 出来的是 Arc 包起来的共享 ModelProvider。

调用关系:这是本文件最常用的工厂函数;大量测试都从它开始。它会调用 is_amazon_bedrock,并把创建工作交给对应 provider 的 new。

调用图:调用 3 个内部函数(is_amazon_bedrock, new, new);被 15 处调用(amazon_bedrock_provider_creates_static_models_manager, amazon_bedrock_provider_returns_bedrock_account_state, configured_bedrock_catalog_only_allows_default_service_tier, configured_provider_models_manager_uses_provider_bearer_token, configured_provider_runtime_base_url_uses_configured_base_url, configured_provider_uses_default_approval_review_preferred_model, configured_provider_uses_default_capabilities, create_model_provider_builds_command_auth_manager_without_base_manager, create_model_provider_does_not_use_openai_auth_manager_for_amazon_bedrock_provider, create_model_provider_uses_managed_auth_for_amazon_bedrock_provider (+5 more));外部调用 1 个(new)。

ConfiguredModelProvider::new207–213 ↗
fn new(provider_info: ModelProviderInfo, auth_manager: Option<Arc<AuthManager>>) -> Self

作用:创建通用配置型服务商对象。它会先把传入的认证管理器调整成适合这个服务商使用的版本。

数据流:进去的是 provider_info 和可选 auth_manager → 它调用 auth_manager_for_provider 过滤或生成合适的认证管理器 → 出来的是保存好配置和认证管理器的 ConfiguredModelProvider。

调用关系:create_model_provider 在非 Amazon Bedrock 场景会调用它;认证选择的细节交给 auth_manager_for_provider。

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

ConfiguredModelProvider::info217–219 ↗
fn info(&self) -> &ModelProviderInfo

作用:返回这个服务商保存的原始配置信息。其他流程靠它知道地址、认证要求、重试设置等。

数据流:进去的是 ConfiguredModelProvider 自身 → 它借出内部的 ModelProviderInfo → 出来的是配置引用,不会修改任何内容。

调用关系:这是对 ModelProvider::info 的具体实现;默认的 api_provider、runtime_base_url、api_auth 等接口方法都会依赖 info。

ConfiguredModelProvider::auth_manager221–223 ↗
fn auth_manager(&self) -> Option<Arc<AuthManager>>

作用:返回这个服务商可用的认证管理器。如果这个服务商不需要或不能用该认证方式,就返回空。

数据流:进去的是 ConfiguredModelProvider 自身 → 它克隆一份 Arc 指针,也就是共享引用 → 出来的是 Option<Arc<AuthManager>>,内部认证数据本身不会被复制。

调用关系:这是 ModelProvider 接口实现;测试会用它确认命令认证被创建、Bedrock 不误用 OpenAI 认证。

ConfiguredModelProvider::supports_attestation225–230 ↗
fn supports_attestation(&self) -> bool

作用:判断这个通用服务商当前是否支持 attestation。这里的规则是:如果缓存里的认证是 ChatGPT 登录,就支持。

数据流:进去的是 ConfiguredModelProvider → 它查看 auth_manager,读取缓存认证 auth_cached,再判断是否是 ChatGPT auth → 出来是 true 或 false。

调用关系:它覆盖了 ModelProvider 的默认 false;不等待网络刷新,只看缓存,所以适合快速决定请求是否能带证明。

ConfiguredModelProvider::auth232–239 ↗
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>>

作用:异步取出当前服务商的认证信息。没有认证管理器时就表示没有凭证。

数据流:进去的是 ConfiguredModelProvider → 它检查 auth_manager;有就等待 auth_manager.auth().await,没就返回 None → 出来的是可选 CodexAuth。

调用关系:这是 ModelProvider::auth 的具体实现;默认的 api_provider 和 api_auth 会通过它拿当前凭证。

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

ConfiguredModelProvider::account_state241–281 ↗
fn account_state(&self) -> ProviderAccountResult

作用:把底层认证信息整理成应用界面能展示的账号状态。它会区分 API Key、ChatGPT 账号、无账号,以及不支持的 Bedrock Key。

数据流:进去的是 ConfiguredModelProvider → 它先看该服务商是否 requires_openai_auth;如果需要,就从缓存认证里取账号,并跳过刷新失败的认证;再按认证类型生成 ProviderAccount 或错误 → 出来的是 ProviderAccountState,或 ProviderAccountError。

调用关系:这是 ModelProvider::account_state 的具体实现;多组账号相关测试都验证它在 OpenAI、ChatGPT、Bedrock Key、自定义服务商下的表现。

ConfiguredModelProvider::models_manager283–305 ↗
fn models_manager(
        &self,
        codex_home: PathBuf,
        config_model_catalog: Option<ModelsResponse>,
    ) -> SharedModelsManager

作用:为这个服务商创建“模型列表管理器”。它决定模型列表是直接用配置里的静态清单,还是去远端接口下载。

数据流:进去的是 codex_home 路径和可选 config_model_catalog → 如果配置里有模型清单,就创建 StaticModelsManager;否则创建 OpenAiModelsEndpoint,再创建 OpenAiModelsManager 去拉远端 /models → 出来的是 SharedModelsManager。

调用关系:这是 ModelProvider::models_manager 的具体实现;它把模型查询工作交给 StaticModelsManager 或 OpenAiModelsManager,并把认证管理器传给它们。

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

tests::provider_info_with_command_auth330–345 ↗
fn provider_info_with_command_auth() -> ModelProviderInfo

作用:为测试造一个带“外部命令取 token”认证方式的服务商配置。这样可以检查没有基础认证管理器时也能生成命令认证。

数据流:进去没有参数 → 它构造 ModelProviderAuthInfo,命令名是 print-token,并填入当前目录、超时和刷新间隔 → 出来的是一个 OpenAI 兼容的 ModelProviderInfo。

调用关系:create_model_provider_builds_command_auth_manager_without_base_manager 会调用它,再交给 create_model_provider 验证认证管理器是否创建成功。

调用图:调用 1 个内部函数(create_openai_provider);外部调用 3 个(new, new, current_dir)。

tests::test_codex_home347–349 ↗
fn test_codex_home() -> std::path::PathBuf

作用:给测试生成一个临时的 Codex 主目录路径,避免测试写到真实用户目录。

数据流:进去没有参数 → 它读取系统临时目录,并拼上当前进程 ID → 出来的是一个 PathBuf 路径。

调用关系:模型管理器相关测试会调用它,把临时目录传给 provider.models_manager。

调用图:外部调用 2 个(format!, temp_dir)。

tests::provider_for351–371 ↗
fn provider_for(base_url: String) -> ModelProviderInfo

作用:为测试快速造一个指向指定 base_url 的假服务商配置。常用于把服务商指到 mock server。

数据流:进去的是 base_url 字符串 → 它填好名字、URL、协议类型、重试次数和认证字段 → 出来的是 ModelProviderInfo。

调用关系:runtime_base_url 测试和远端模型列表测试会调用它,再把结果交给 create_model_provider。

tests::remote_model373–398 ↗
fn remote_model(slug: &str) -> ModelInfo

作用:为测试造一个假的远端模型信息。传入模型 slug,就得到一条完整 ModelInfo。

数据流:进去的是 slug 字符串 → 它用 json! 拼出模型 JSON,再用 serde_json::from_value 转成 ModelInfo → 出来的是测试可用的模型对象。

调用关系:configured_provider_models_manager_uses_provider_bearer_token 用它生成 mock server 返回的模型列表。

调用图:外部调用 2 个(json!, from_value)。

tests::bedrock_api_key_auth400–405 ↗
fn bedrock_api_key_auth() -> CodexAuth

作用:为测试造一个 Amazon Bedrock API Key 类型的认证对象。

数据流:进去没有参数 → 它填入测试用 api_key 和 region → 出来的是 CodexAuth::BedrockApiKey。

调用关系:Bedrock 认证相关测试会调用它,用来确认 Bedrock provider 能使用该认证,而 OpenAI provider 会拒绝该账号状态。

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

tests::configured_provider_uses_default_capabilities408–415 ↗
fn configured_provider_uses_default_capabilities()

作用:测试通用服务商没有特殊设置时,会使用默认能力开关。

数据流:进去没有参数 → 它创建 OpenAI provider,再读取 provider.capabilities() → 断言结果等于 ProviderCapabilities::default。

调用关系:它通过 create_model_provider 构造对象,间接验证 ModelProvider::capabilities 的默认行为。

调用图:调用 2 个内部函数(create_openai_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::configured_provider_uses_default_approval_review_preferred_model418–428 ↗
fn configured_provider_uses_default_approval_review_preferred_model()

作用:测试通用服务商默认的自动审批复查模型名是否正确。

数据流:进去没有参数 → 它创建 OpenAI provider,调用 approval_review_preferred_model → 断言返回 DEFAULT_APPROVAL_REVIEW_PREFERRED_MODEL。

调用关系:它通过 create_model_provider 走正常创建路径,验证 ModelProvider 默认方法没有被意外改坏。

调用图:调用 2 个内部函数(create_openai_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::configured_provider_runtime_base_url_uses_configured_base_url431–444 ↗
async fn configured_provider_runtime_base_url_uses_configured_base_url()

作用:测试运行时请求地址会使用配置里写的 base_url。

数据流:进去没有参数 → 它用 provider_for 造一个带 https://example.test/v1 的配置,创建 provider,然后等待 runtime_base_url → 断言返回同一个 URL。

调用关系:它验证 ModelProvider::runtime_base_url 的默认实现,也证明配置值能一路传到运行时。

调用图:调用 1 个内部函数(create_model_provider);外部调用 2 个(assert_eq!, provider_for)。

tests::create_model_provider_builds_command_auth_manager_without_base_manager447–458 ↗
fn create_model_provider_builds_command_auth_manager_without_base_manager()

作用:测试即使没有传入现成认证管理器,只要配置了命令认证,也会创建可用的认证管理器。

数据流:进去没有参数 → 它用 provider_info_with_command_auth 造配置,传 None 给 create_model_provider,再取 auth_manager → 断言它存在且 has_external_auth 为真。

调用关系:它覆盖 create_model_provider 到 ConfiguredModelProvider::new,再到 auth_manager_for_provider 的这条创建链路。

调用图:调用 1 个内部函数(create_model_provider);外部调用 2 个(assert!, provider_info_with_command_auth)。

tests::create_model_provider_does_not_use_openai_auth_manager_for_amazon_bedrock_provider461–473 ↗
fn create_model_provider_does_not_use_openai_auth_manager_for_amazon_bedrock_provider()

作用:测试 Amazon Bedrock 服务商不会误用 OpenAI 的 API Key 认证管理器。

数据流:进去没有参数 → 它创建 Bedrock provider 配置,同时传入一个 OpenAI API Key 的 AuthManager → 断言 provider.auth_manager() 是空。

调用关系:它验证 create_model_provider 选择 Bedrock 专门实现后,不会走通用 OpenAI 认证逻辑。

调用图:调用 4 个内部函数(from_auth_for_testing, from_api_key, create_amazon_bedrock_provider, create_model_provider);外部调用 1 个(assert!)。

tests::create_model_provider_uses_managed_auth_for_amazon_bedrock_provider476–484 ↗
async fn create_model_provider_uses_managed_auth_for_amazon_bedrock_provider()

作用:测试 Amazon Bedrock 服务商在合适情况下能使用传入的 Bedrock API Key 认证。

数据流:进去没有参数 → 它用 bedrock_api_key_auth 造认证,创建 Bedrock provider,再异步读取 provider.auth() → 断言读到的认证和传入的一样。

调用关系:它验证 create_model_provider 创建 Bedrock provider 后,Bedrock 自己的认证读取路径是通的。

调用图:调用 3 个内部函数(from_auth_for_testing, create_amazon_bedrock_provider, create_model_provider);外部调用 2 个(assert_eq!, bedrock_api_key_auth)。

tests::openai_provider_returns_unauthenticated_openai_account_state487–500 ↗
fn openai_provider_returns_unauthenticated_openai_account_state()

作用:测试 OpenAI provider 没有登录信息时,账号状态会显示“需要 OpenAI 认证,但当前没有账号”。

数据流:进去没有参数 → 它创建没有 AuthManager 的 OpenAI provider,调用 account_state → 断言 account 为 None 且 requires_openai_auth 为 true。

调用关系:它验证 ConfiguredModelProvider::account_state 在未登录 OpenAI 场景下的行为。

调用图:调用 2 个内部函数(create_openai_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::openai_provider_returns_api_key_account_state503–518 ↗
fn openai_provider_returns_api_key_account_state()

作用:测试 OpenAI provider 使用 API Key 时,账号状态能正确显示为 API Key。

数据流:进去没有参数 → 它创建带 OpenAI API Key 的 AuthManager,再创建 provider 并调用 account_state → 断言 account 是 ProviderAccount::ApiKey。

调用关系:它验证 ConfiguredModelProvider::account_state 对 CodexAuth::ApiKey 的转换。

调用图:调用 4 个内部函数(from_auth_for_testing, from_api_key, create_openai_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::openai_provider_rejects_chatgpt_account_state_without_email521–533 ↗
fn openai_provider_rejects_chatgpt_account_state_without_email()

作用:测试 ChatGPT 登录如果缺少邮箱或套餐信息,账号状态会报错,而不是展示一个不完整账号。

数据流:进去没有参数 → 它用测试用的 dummy ChatGPT auth 创建 provider,调用 account_state → 断言返回 MissingChatgptAccountDetails。

调用关系:它验证 ConfiguredModelProvider::account_state 对 ChatGPT 类认证的严格检查。

调用图:调用 4 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, create_openai_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::openai_provider_rejects_bedrock_api_key_account_state536–546 ↗
fn openai_provider_rejects_bedrock_api_key_account_state()

作用:测试 OpenAI provider 不接受 Bedrock API Key 作为自己的账号状态。

数据流:进去没有参数 → 它把 bedrock_api_key_auth 放进 AuthManager,再创建 OpenAI provider,调用 account_state → 断言返回 UnsupportedBedrockApiKeyAuth。

调用关系:它验证 ConfiguredModelProvider::account_state 不会把 Bedrock 凭证误认为 OpenAI 凭证。

调用图:调用 3 个内部函数(from_auth_for_testing, create_openai_provider, create_model_provider);外部调用 2 个(assert_eq!, bedrock_api_key_auth)。

tests::custom_non_openai_provider_returns_no_account_state549–568 ↗
fn custom_non_openai_provider_returns_no_account_state()

作用:测试不要求 OpenAI 登录的自定义服务商,不会显示 OpenAI 账号状态。

数据流:进去没有参数 → 它构造一个 requires_openai_auth 为 false 的本地服务商配置,创建 provider 并调用 account_state → 断言 account 为 None 且 requires_openai_auth 为 false。

调用关系:它验证 ConfiguredModelProvider::account_state 的分支:只有需要 OpenAI 认证时才解析账号。

调用图:调用 1 个内部函数(create_model_provider);外部调用 2 个(default, assert_eq!)。

tests::amazon_bedrock_provider_returns_bedrock_account_state571–587 ↗
fn amazon_bedrock_provider_returns_bedrock_account_state()

作用:测试 Amazon Bedrock provider 会显示 Bedrock 账号状态,并说明凭证来自 AWS 托管方式。

数据流:进去没有参数 → 它创建 Bedrock provider,调用 account_state → 断言 account 是 AmazonBedrock 且 credential_source 是 AwsManaged。

调用关系:它通过 create_model_provider 走 Bedrock 专门实现,验证 Bedrock 的账号状态不是通用 provider 那套逻辑。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, create_model_provider);外部调用 1 个(assert_eq!)。

tests::amazon_bedrock_provider_creates_static_models_manager590–615 ↗
async fn amazon_bedrock_provider_creates_static_models_manager()

作用:测试 Amazon Bedrock provider 会创建静态模型列表,并且默认模型符合预期。

数据流:进去没有参数 → 它创建 Bedrock provider 和 models_manager,在线策略读取 raw_model_catalog,再列出模型和默认模型 → 断言模型 ID 和默认模型正确。

调用关系:它验证 Bedrock provider 的 models_manager 行为;测试中用 test_codex_home 提供临时目录。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, create_model_provider);外部调用 2 个(assert_eq!, test_codex_home)。

tests::configured_bedrock_catalog_only_allows_default_service_tier618–649 ↗
async fn configured_bedrock_catalog_only_allows_default_service_tier()

作用:测试给 Bedrock 配置模型清单时,会去掉额外速度档和服务档,只保留默认可用形式。

数据流:进去没有参数 → 它从内置模型里拿 gpt-5.5,确认原本有服务档信息;再创建 Bedrock provider 并传入这个模型清单 → 读取 catalog 后断言额外档位、服务档和默认服务档都被清空。

调用关系:它验证 Bedrock provider 或其模型管理器对配置模型清单的特殊收敛规则,避免暴露 Bedrock 不支持的服务档选择。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, create_model_provider);外部调用 5 个(assert!, assert_eq!, bundled_models_response, test_codex_home, vec!)。

tests::configured_provider_models_manager_uses_provider_bearer_token652–689 ↗
async fn configured_provider_models_manager_uses_provider_bearer_token()

作用:测试通用 provider 拉远端模型列表时,会优先使用服务商配置里的 bearer token。bearer token 可以理解为 HTTP 请求头里的通行证。

数据流:进去没有参数 → 它启动 mock server,要求 GET /models 必须带 Authorization: Bearer provider-token;然后创建带 experimental_bearer_token 的 provider,调用 models_manager 拉模型列表 → 断言返回里包含 provider-model。

调用关系:它验证 ConfiguredModelProvider::models_manager 创建的 OpenAiModelsManager 会通过 OpenAiModelsEndpoint 使用 provider 自己的 token,而不是误用传入的 ChatGPT 测试认证。

调用图:调用 3 个内部函数(from_auth_for_testing, create_dummy_chatgpt_auth_for_testing, create_model_provider);外部调用 10 个(given, start, new, assert!, provider_for, test_codex_home, vec!, header_regex, method, path)。

通用提供方认证

构建非 Bedrock 模型提供方使用的可复用认证基础组件和基于身份的令牌来源。

agent-identity/src/lib.rs源码 ↗
domain_logiccross-cutting:启动注册、任务请求、身份验证时都会用到

可以把这个文件想成代理的证件办公室。它会生成代理用的私钥和公钥,私钥像印章一样留在本地,公钥交给服务器识别;注册新任务时,它用私钥给时间戳签名,证明请求不是别人伪造的;访问某个任务时,它再做一个带签名的授权头,放进 HTTP 请求里。它还会读取和校验 JWT(JSON Web Token,一种带声明和签名的身份票据),必要时从服务器下载 JWKS(公开钥列表,用来验票)。比较重要的是:如果没有提供 JWKS,它只会解开 JWT 内容,不验证签名;如果提供了 JWKS,就会检查签名、签发方和接收方,安全性更强。

函数细节37
authorization_header_for_agent_task106–126 ↗
fn authorization_header_for_agent_task(
    key: AgentIdentityKey<'_>,
    target: AgentTaskAuthorizationTarget<'_>,
) -> Result<String>

作用:为某个代理任务生成 HTTP Authorization 头。它的作用是把“我是这个代理,而且我有权访问这个任务”做成一张带签名的通行证。

数据流:输入代理保存的身份密钥和目标任务信息 → 先确认两边的代理运行 ID 一致,再取当前时间,用私钥给“代理 ID、任务 ID、时间”签名,最后把这些信息打包编码 → 输出形如 AgentAssertion ... 的请求头字符串;不改动外部状态。

调用关系:它是任务访问前的最后一步包装。内部把签名工作交给 sign_agent_assertion_payload,把打包编码交给 serialize_agent_assertion;测试会调用它确认正常签名和 ID 不匹配时报错。

调用图:调用 2 个内部函数(serialize_agent_assertion, sign_agent_assertion_payload);被 2 处调用(authorization_header_for_agent_task_rejects_mismatched_runtime, authorization_header_for_agent_task_serializes_signed_agent_assertion);外部调用 3 个(now, ensure!, format!)。

fetch_agent_identity_jwks128–145 ↗
async fn fetch_agent_identity_jwks(
    client: &reqwest::Client,
    chatgpt_base_url: &str,
) -> Result<JwkSet>

作用:从 ChatGPT/Codex 后端下载 JWKS,也就是一组公开钥。后面验证 JWT 签名时需要这些公开钥来判断票据是不是可信服务器签的。

数据流:输入 HTTP 客户端和后端基础地址 → 拼出 JWKS 地址,发 GET 请求,设置 10 秒超时,检查 HTTP 状态,再把返回的 JSON 解成 JWK 集合 → 输出可用于验签的 JwkSet

调用关系:它负责拿到验票用的钥匙。它把 URL 拼接交给 agent_identity_jwks_url,拿到结果后通常会交给 decode_agent_identity_jwt 使用。

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

decode_agent_identity_jwt147–171 ↗
fn decode_agent_identity_jwt(
    jwt: &str,
    jwks: Option<&JwkSet>,
) -> Result<AgentIdentityJwtClaims>

作用:解读代理身份 JWT,并在有公开钥时验证它。JWT 可以理解为服务器签发的一张身份票据,里面写着代理 ID、账号、邮箱、套餐等信息。

数据流:输入 JWT 字符串和可选的 JWKS → 如果没有 JWKS,就只解开票据内容;如果有 JWKS,就读 JWT 头里的 key id,找到对应公开钥,检查签名、签发方和接收方 → 输出 AgentIdentityJwtClaims,也就是票据里的身份声明。

调用关系:这是身份票据进入系统后的主入口。没有 JWKS 时把简单解码交给 decode_agent_identity_jwt_payload;有 JWKS 时使用外部 JWT 库验签。多个测试覆盖了正常读取、套餐别名、可信 key、不可信 key、缺少签发方或接收方等情况。

调用图:调用 1 个内部函数(decode_agent_identity_jwt_payload);被 4 处调用(decode_agent_identity_jwt_maps_raw_plan_aliases, decode_agent_identity_jwt_reads_claims, decode_agent_identity_jwt_rejects_untrusted_kid, decode_agent_identity_jwt_requires_issuer_and_audience);外部调用 3 个(from_jwk, new, decode_header)。

decode_agent_identity_jwt_payload173–185 ↗
fn decode_agent_identity_jwt_payload(jwt: &str) -> Result<T>

作用:只解开 JWT 的中间内容,不验证签名。它适合在已经信任来源,或者测试场景下读取票据内容。

数据流:输入 JWT 字符串 → 按点号拆成头、内容、签名三段,检查格式,再把中间内容按 base64url 解码,并解析成 JSON → 输出调用者指定的数据类型;格式不对就报错。

调用关系:它是 decode_agent_identity_jwt 的备用路线:当没有传入 JWKS 时被调用。它不负责安全验签,只负责把内容读出来。

调用图:被 1 处调用(decode_agent_identity_jwt);外部调用 3 个(bail!, ensure!, from_slice)。

sign_task_registration_payload187–194 ↗
fn sign_task_registration_payload(
    key: AgentIdentityKey<'_>,
    timestamp: &str,
) -> Result<String>

作用:给“注册任务”请求生成签名。这个签名像盖章,告诉服务器:这个请求确实来自持有该代理私钥的一方。

数据流:输入代理身份密钥和时间戳 → 先把保存的 base64 私钥还原成签名密钥,再拼出“代理 ID:时间戳”的文本并签名 → 输出 base64 格式的签名字符串。

调用关系:它被 register_agent_task 调用,用在真正发注册请求之前。私钥解析工作交给 signing_key_from_private_key_pkcs8_base64

调用图:调用 1 个内部函数(signing_key_from_private_key_pkcs8_base64);被 1 处调用(register_agent_task);外部调用 1 个(format!)。

register_agent_task196–232 ↗
async fn register_agent_task(
    client: &reqwest::Client,
    chatgpt_base_url: &str,
    key: AgentIdentityKey<'_>,
) -> Result<String>

作用:向后端登记一个新的代理任务,并拿回任务 ID。它相当于代理去服务台报到,换取后续访问任务时要用的编号。

数据流:输入 HTTP 客户端、后端基础地址和代理密钥 → 取当前时间,生成注册签名,拼出注册 URL,发送 POST JSON 请求;如果服务器报错,就带上状态码和截断后的响应内容报错;成功后解析响应 → 输出明文任务 ID。

调用关系:它串起任务注册的完整流程。URL 由 agent_task_registration_url 生成,签名由 sign_task_registration_payload 生成,响应里的任务 ID 由 task_id_from_register_task_response 取出或解密。

调用图:调用 3 个内部函数(agent_task_registration_url, sign_task_registration_payload, task_id_from_register_task_response);外部调用 4 个(now, bail!, post, format!)。

task_id_from_register_task_response234–246 ↗
fn task_id_from_register_task_response(
    key: AgentIdentityKey<'_>,
    response: RegisterTaskResponse,
) -> Result<String>

作用:从注册任务的服务器响应里取出任务 ID。服务器可能直接给明文,也可能给加密后的任务 ID,这个函数统一处理。

数据流:输入代理密钥和注册响应 → 先找 task_idtaskId;如果没有,就找 encrypted_task_idencryptedTaskId 并解密 → 输出最终可用的任务 ID 字符串。

调用关系:它是 register_agent_task 解析响应的收尾步骤。遇到加密任务 ID 时,把解密工作交给 decrypt_task_id_response

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

decrypt_task_id_response248–260 ↗
fn decrypt_task_id_response(
    key: AgentIdentityKey<'_>,
    encrypted_task_id: &str,
) -> Result<String>

作用:把服务器返回的加密任务 ID 解成明文。这样即使响应被别人看到,也不能直接知道任务编号。

数据流:输入代理密钥和 base64 加密文本 → 还原私钥,解码密文,把签名密钥转换成 Curve25519 解密密钥,再尝试打开密文,最后按 UTF-8 文本读取 → 输出明文任务 ID。

调用关系:它被 task_id_from_register_task_response 在需要解密时调用。它依赖 signing_key_from_private_key_pkcs8_base64 读私钥,并依赖 curve25519_secret_key_from_signing_key 得到解密用的密钥。

调用图:调用 2 个内部函数(curve25519_secret_key_from_signing_key, signing_key_from_private_key_pkcs8_base64);被 1 处调用(task_id_from_register_task_response);外部调用 1 个(from_utf8)。

generate_agent_key_material262–276 ↗
fn generate_agent_key_material() -> Result<GeneratedAgentKeyMaterial>

作用:生成一套新的代理身份密钥材料。简单说,就是给新代理发一枚私章和一张可公开的公钥名片。

数据流:无业务输入 → 从操作系统安全随机数里取 32 字节,做成 Ed25519 签名私钥,把私钥编码成 PKCS#8 再转 base64,同时把公钥编码成 SSH 公钥格式 → 输出包含私钥和公钥的 GeneratedAgentKeyMaterial

调用关系:它通常在代理注册或创建身份时使用。公钥格式化工作交给 encode_ssh_ed25519_public_key

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

public_key_ssh_from_private_key_pkcs8_base64278–283 ↗
fn public_key_ssh_from_private_key_pkcs8_base64(
    private_key_pkcs8_base64: &str,
) -> Result<String>

作用:从已经保存的私钥里重新算出 SSH 格式公钥。这样不必额外保存公钥,也能随时拿出来给服务器或日志使用。

数据流:输入 base64 的 PKCS#8 私钥 → 解析成签名密钥,取出对应公钥,再编码成 SSH 常见格式 → 输出类似 ssh-ed25519 ... 的字符串。

调用关系:它是密钥派生的小工具。先调用 signing_key_from_private_key_pkcs8_base64 读私钥,再调用 encode_ssh_ed25519_public_key 生成展示/传输格式。

调用图:调用 2 个内部函数(encode_ssh_ed25519_public_key, signing_key_from_private_key_pkcs8_base64)。

verifying_key_from_private_key_pkcs8_base64285–290 ↗
fn verifying_key_from_private_key_pkcs8_base64(
    private_key_pkcs8_base64: &str,
) -> Result<VerifyingKey>

作用:从私钥里取出验证签名用的公钥对象。验证钥匙可以用来检查某段数据是不是这把私钥签出来的。

数据流:输入 base64 的 PKCS#8 私钥 → 解析成签名密钥,再取出它配套的验证钥匙 → 输出 VerifyingKey

调用关系:它是给其他代码复用的密钥转换入口。具体私钥解析交给 signing_key_from_private_key_pkcs8_base64

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

curve25519_secret_key_from_private_key_pkcs8_base64292–297 ↗
fn curve25519_secret_key_from_private_key_pkcs8_base64(
    private_key_pkcs8_base64: &str,
) -> Result<Curve25519SecretKey>

作用:从代理签名私钥派生出 Curve25519 解密私钥。Curve25519 是一种常用的加密曲线,这里用于解开服务器加密给代理的内容。

数据流:输入 base64 的 PKCS#8 私钥 → 先解析成 Ed25519 签名密钥,再按固定算法转换成 Curve25519 私钥 → 输出可用于解密的 Curve25519SecretKey

调用关系:它是公开的转换工具。内部先用 signing_key_from_private_key_pkcs8_base64 读私钥,再把转换交给 curve25519_secret_key_from_signing_key

调用图:调用 2 个内部函数(curve25519_secret_key_from_signing_key, signing_key_from_private_key_pkcs8_base64)。

agent_registration_url299–302 ↗
fn agent_registration_url(chatgpt_base_url: &str) -> String

作用:拼出代理注册接口的完整地址。它避免调用方到处手写路径,减少少写斜杠或多写斜杠的问题。

数据流:输入后端基础地址 → 去掉末尾多余的 /,再接上 /v1/agent/register → 输出完整注册 URL 字符串。

调用关系:它是 URL 小工具,通常在代理首次注册身份时会被上层流程使用。

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

agent_task_registration_url304–307 ↗
fn agent_task_registration_url(chatgpt_base_url: &str, agent_runtime_id: &str) -> String

作用:拼出某个代理注册任务的接口地址。地址里会带上代理运行 ID,让服务器知道是哪一个代理在报到。

数据流:输入后端基础地址和代理运行 ID → 去掉基础地址末尾的 /,再拼上 /v1/agent/{agent_runtime_id}/task/register → 输出完整任务注册 URL。

调用关系:它被 register_agent_task 调用,是发 POST 注册请求前的地址生成步骤。

调用图:被 1 处调用(register_agent_task);外部调用 1 个(format!)。

agent_identity_biscuit_url309–312 ↗
fn agent_identity_biscuit_url(chatgpt_base_url: &str) -> String

作用:拼出用于代理身份认证的 authenticate_app_v2 地址。这里的 biscuit 可以理解为认证流程里用到的一种令牌入口。

数据流:输入后端基础地址 → 去掉末尾斜杠,接上 /authenticate_app_v2 → 输出认证 URL 字符串。

调用关系:它是给身份认证流程使用的 URL 工具。本文件没有进一步调用它,但外部模块可以用它定位认证接口。

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

agent_identity_jwks_url314–321 ↗
fn agent_identity_jwks_url(chatgpt_base_url: &str) -> String

作用:拼出下载代理身份 JWKS 的地址。它会根据基础地址类型选择不同路径,兼容 ChatGPT 后端和 Codex API 两种部署方式。

数据流:输入后端基础地址 → 去掉末尾斜杠;如果地址里包含 /backend-api,就接 /wham/agent-identities/jwks,否则接 /agent-identities/jwks → 输出 JWKS URL。

调用关系:它被 fetch_agent_identity_jwks 调用。对应测试会检查 backend-api 和 codex-api 两类地址都能拼对。

调用图:被 1 处调用(fetch_agent_identity_jwks);外部调用 1 个(format!)。

agent_identity_request_id323–332 ↗
fn agent_identity_request_id() -> Result<String>

作用:生成一个随机的代理身份请求 ID。这个 ID 像一张临时排队号,方便追踪某次身份请求。

数据流:无业务输入 → 从操作系统安全随机数生成 16 字节,转成不带填充的 URL 安全 base64,并加上 codex-agent-identity- 前缀 → 输出请求 ID 字符串。

调用关系:它是跨流程可用的小工具,常用于发起身份相关请求前生成唯一标识。

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

build_abom334–349 ↗
fn build_abom(session_source: SessionSource) -> AgentBillOfMaterials

作用:构造代理的物料清单 ABOM,也就是告诉服务器“这个代理是什么版本、从哪里启动、跑在哪个系统上”。

数据流:输入会话来源,比如 VS Code、CLI 或其他来源 → 读取当前包版本,根据来源选择 harness ID,再把来源和操作系统拼成运行位置 → 输出 AgentBillOfMaterials

调用关系:它通常在注册或上报身份信息时使用,让后端知道代理环境。它不调用本文件其他复杂逻辑,只汇总已有环境信息。

调用图:外部调用 2 个(env!, format!)。

encode_ssh_ed25519_public_key351–356 ↗
fn encode_ssh_ed25519_public_key(verifying_key: &VerifyingKey) -> String

作用:把 Ed25519 公钥编码成常见的 SSH 公钥文本格式。这样公钥可以像 ssh-ed25519 ... 一样被存储、显示或上传。

数据流:输入验证公钥 → 按 SSH 规则写入算法名和 32 字节公钥,每段前面带长度,再整体 base64 → 输出 SSH 公钥字符串。

调用关系:它被 generate_agent_key_materialpublic_key_ssh_from_private_key_pkcs8_base64 调用。内部用 append_ssh_string 按 SSH 二进制格式塞入字段。

调用图:调用 1 个内部函数(append_ssh_string);被 2 处调用(generate_agent_key_material, public_key_ssh_from_private_key_pkcs8_base64);外部调用 3 个(with_capacity, as_bytes, format!)。

sign_agent_assertion_payload358–366 ↗
fn sign_agent_assertion_payload(
    key: AgentIdentityKey<'_>,
    task_id: &str,
    timestamp: &str,
) -> Result<String>

作用:给任务授权声明生成真正的签名。它证明“这个代理 ID、这个任务 ID、这个时间”是持有私钥的人认可的。

数据流:输入代理密钥、任务 ID 和时间戳 → 还原私钥,拼出“代理 ID:任务 ID:时间戳”的文本并签名,最后 base64 编码 → 输出签名字符串。

调用关系:它只做签名这一件事,被 authorization_header_for_agent_task 调用。私钥读取交给 signing_key_from_private_key_pkcs8_base64

调用图:调用 1 个内部函数(signing_key_from_private_key_pkcs8_base64);被 1 处调用(authorization_header_for_agent_task);外部调用 1 个(format!)。

serialize_agent_assertion368–377 ↗
fn serialize_agent_assertion(envelope: &AgentAssertionEnvelope) -> Result<String>

作用:把任务授权声明打包成可以放进 HTTP 头里的短文本。它负责把结构化信息变成稳定的 JSON,再做 URL 安全编码。

数据流:输入包含代理 ID、任务 ID、时间戳、签名的 envelope → 用有序 map 固定字段顺序,序列化成 JSON 字节,再用 base64url 无填充编码 → 输出令牌字符串。

调用关系:它被 authorization_header_for_agent_task 调用,是生成 AgentAssertion 请求头的最后包装步骤。

调用图:被 1 处调用(authorization_header_for_agent_task);外部调用 2 个(from, to_vec)。

curve25519_secret_key_from_signing_key379–387 ↗
fn curve25519_secret_key_from_signing_key(signing_key: &SigningKey) -> Curve25519SecretKey

作用:把 Ed25519 签名密钥转换成 Curve25519 解密密钥。通俗说,就是从同一份秘密材料派生出另一把用途不同的钥匙。

数据流:输入 Ed25519 签名密钥 → 对私钥字节做 SHA-512 哈希,再取前 32 字节并按 Curve25519 规则调整部分位 → 输出 Curve25519 私钥。

调用关系:它是底层密码学转换函数。decrypt_task_id_response 用它来解密任务 ID,curve25519_secret_key_from_private_key_pkcs8_base64 用它提供公开转换能力。

调用图:被 2 处调用(curve25519_secret_key_from_private_key_pkcs8_base64, decrypt_task_id_response);外部调用 3 个(from, digest, to_bytes)。

append_ssh_string389–392 ↗
fn append_ssh_string(buf: &mut Vec<u8>, value: &[u8])

作用:按 SSH 二进制格式往缓冲区里追加一段字符串。SSH 格式要求先写长度,再写内容。

数据流:输入可变字节数组和要写入的字节 → 先追加 4 字节大端长度,再追加实际内容 → 原缓冲区被扩展,不返回新值。

调用关系:它是 encode_ssh_ed25519_public_key 的小零件,用来正确组装 SSH 公钥里的算法名和公钥字节。

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

signing_key_from_private_key_pkcs8_base64394–400 ↗
fn signing_key_from_private_key_pkcs8_base64(private_key_pkcs8_base64: &str) -> Result<SigningKey>

作用:把保存成文本的私钥还原成可用的签名密钥。很多函数都先经过它,才能真正签名、取公钥或派生解密钥。

数据流:输入 base64 的 PKCS#8 私钥文本 → 先 base64 解码,再按 PKCS#8 私钥格式解析成 Ed25519 签名密钥 → 输出 SigningKey;编码或格式错就报错。

调用关系:它是本文件的密钥读取基础设施。签任务注册、签任务授权、解密任务 ID、导出公钥、导出验证钥匙、派生 Curve25519 密钥都会调用它。

调用图:被 6 处调用(curve25519_secret_key_from_private_key_pkcs8_base64, decrypt_task_id_response, public_key_ssh_from_private_key_pkcs8_base64, sign_agent_assertion_payload, sign_task_registration_payload, verifying_key_from_private_key_pkcs8_base64);外部调用 1 个(from_pkcs8_der)。

tests::authorization_header_for_agent_task_serializes_signed_agent_assertion416–465 ↗
fn authorization_header_for_agent_task_serializes_signed_agent_assertion()

作用:测试任务授权头是否真的包含正确内容,并且签名能被对应公钥验证。它防止授权头格式或签名规则被无意改坏。

数据流:输入固定测试私钥和任务信息 → 调用 authorization_header_for_agent_task 生成头,解开里面的 token 和 JSON,再用公钥验证签名 → 测试通过表示内容和签名都符合预期。

调用关系:它直接覆盖 authorization_header_for_agent_task 的成功路径,也间接覆盖 sign_agent_assertion_payloadserialize_agent_assertion

调用图:调用 1 个内部函数(authorization_header_for_agent_task);外部调用 5 个(from_slice, from_bytes, assert_eq!, format!, from_slice)。

tests::authorization_header_for_agent_task_rejects_mismatched_runtime468–490 ↗
fn authorization_header_for_agent_task_rejects_mismatched_runtime()

作用:测试代理 ID 不匹配时必须拒绝生成授权头。这样可以防止拿 A 代理的私钥去给 B 代理的任务冒充授权。

数据流:输入一个代理 ID 为 agent-123 的密钥和一个目标代理 ID 为 agent-456 的任务 → 调用 authorization_header_for_agent_task → 预期得到指定错误信息。

调用关系:它覆盖 authorization_header_for_agent_task 开头的安全检查,确保后续签名流程不会在身份不一致时继续执行。

调用图:调用 1 个内部函数(authorization_header_for_agent_task);外部调用 2 个(from_bytes, assert_eq!)。

tests::decode_agent_identity_jwt_reads_claims493–526 ↗
fn decode_agent_identity_jwt_reads_claims()

作用:测试在不提供 JWKS 时,函数能从 JWT 里读出身份声明。它保证基础字段映射没有错。

数据流:输入一个测试构造的 JWT payload → 调用 decode_agent_identity_jwt 且 JWKS 传空 → 输出 claims,并和预期的代理 ID、账号、邮箱、套餐等字段逐一比较。

调用关系:它走的是 decode_agent_identity_jwt 的非验签分支,也间接测试 decode_agent_identity_jwt_payload

调用图:调用 1 个内部函数(decode_agent_identity_jwt);外部调用 3 个(jwt_with_payload, assert_eq!, json!)。

tests::decode_agent_identity_jwt_maps_raw_plan_aliases529–547 ↗
fn decode_agent_identity_jwt_maps_raw_plan_aliases()

作用:测试套餐字段里的旧别名或原始别名能映射成系统认识的套餐类型。这里确认 hc 会被识别为企业套餐。

数据流:输入一个 plan_typehc 的测试 JWT → 解码后读取 claims → 检查套餐类型是否变成 Enterprise。

调用关系:它调用 decode_agent_identity_jwt 的非验签路径,重点保护套餐反序列化规则不被破坏。

调用图:调用 1 个内部函数(decode_agent_identity_jwt);外部调用 3 个(jwt_with_payload, assert_eq!, json!)。

tests::decode_agent_identity_jwt_verifies_when_jwks_is_present550–601 ↗
fn decode_agent_identity_jwt_verifies_when_jwks_is_present()

作用:测试提供 JWKS 时,JWT 必须能通过真实签名验证并读出声明。它覆盖更接近生产环境的安全路径。

数据流:输入测试 RSA 私钥签出的 JWT 和匹配的 JWKS → 调用 decode_agent_identity_jwt → 输出 claims,并与预期身份信息比较。

调用关系:它使用 test_jwkstest_jwt_headertest_rsa_encoding_key 准备测试材料,验证 decode_agent_identity_jwt 的验签分支。

调用图:外部调用 7 个(Known, test_jwks, test_jwt_header, test_rsa_encoding_key, assert_eq!, encode, json!)。

tests::decode_agent_identity_jwt_rejects_untrusted_kid604–627 ↗
fn decode_agent_identity_jwt_rejects_untrusted_kid()

作用:测试 JWT 头里的 key id 如果不在可信 JWKS 里,就必须拒绝。这样可以防止陌生钥匙签出来的票据被接受。

数据流:输入一个用 test-key 签的 JWT,但 JWKS 里只有 other-key → 调用 decode_agent_identity_jwt → 预期返回错误。

调用关系:它覆盖 decode_agent_identity_jwt 查找可信 key 的环节,确保 key id 不匹配不会继续验签成功。

调用图:调用 1 个内部函数(decode_agent_identity_jwt);外部调用 5 个(test_jwks, test_jwt_header, test_rsa_encoding_key, encode, json!)。

tests::decode_agent_identity_jwt_requires_issuer_and_audience630–650 ↗
fn decode_agent_identity_jwt_requires_issuer_and_audience()

作用:测试 JWT 缺少签发方 issuer 和接收方 audience 时会被拒绝。签名正确但票据用途不对,也不能放行。

数据流:输入一个没有 issaud 的签名 JWT,以及匹配 JWKS → 调用 decode_agent_identity_jwt → 预期验证失败。

调用关系:它保护 decode_agent_identity_jwt 里对签发方和接收方的强制校验规则。

调用图:调用 1 个内部函数(decode_agent_identity_jwt);外部调用 5 个(test_jwks, test_jwt_header, test_rsa_encoding_key, encode, json!)。

tests::test_jwt_header652–656 ↗
fn test_jwt_header(kid: &str) -> Header

作用:为测试生成带 key id 的 JWT 头。它让测试 JWT 能声明自己是用哪把钥匙签的。

数据流:输入 key id 字符串 → 创建 RS256 算法的 JWT 头,并把 kid 设进去 → 输出 Header

调用关系:它被多个 JWT 验签测试调用,用来配合 test_jwks 选择对应公开钥。

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

tests::test_rsa_encoding_key658–690 ↗
fn test_rsa_encoding_key() -> EncodingKey

作用:为测试提供一把固定 RSA 私钥,用来签 JWT。固定钥匙让测试结果稳定可重复。

数据流:无业务输入 → 从内嵌 PEM 文本解析 RSA 私钥 → 输出 EncodingKey,供测试 JWT 签名使用。

调用关系:它被 JWT 验签相关测试调用,和 test_jwks 里的公开钥配套。

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

tests::test_jwks692–704 ↗
fn test_jwks(kid: &str) -> jsonwebtoken::jwk::JwkSet

作用:为测试生成一份 JWKS 公开钥列表。它模拟服务器发布的可信验签钥匙。

数据流:输入 key id → 构造包含 RSA 公钥参数和该 key id 的 JSON,再解析成 JwkSet → 输出测试用 JWKS。

调用关系:它被 JWT 验签测试调用,用来测试可信 key、不可信 key 等不同情况。

调用图:外部调用 2 个(from_value, json!)。

tests::agent_identity_jwks_url_uses_backend_api_base_url707–716 ↗
fn agent_identity_jwks_url_uses_backend_api_base_url()

作用:测试当基础地址是 ChatGPT 的 /backend-api 时,JWKS URL 会走特殊的 /wham/... 路径。

数据流:输入两种带或不带末尾斜杠的 backend-api 地址 → 调用 agent_identity_jwks_url → 检查输出完全等于预期地址。

调用关系:它保护 agent_identity_jwks_url 对 backend-api 部署方式的兼容逻辑。

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

tests::agent_identity_jwks_url_uses_codex_api_base_url719–728 ↗
fn agent_identity_jwks_url_uses_codex_api_base_url()

作用:测试普通 Codex API 基础地址会拼成标准 JWKS 路径。它防止 URL 拼接在另一种部署方式下出错。

数据流:输入两种带或不带末尾斜杠的 Codex API 地址 → 调用 agent_identity_jwks_url → 检查输出是否为 /agent-identities/jwks 路径。

调用关系:它和 backend-api 的测试一起覆盖 agent_identity_jwks_url 的两个分支。

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

tests::jwt_with_payload730–736 ↗
fn jwt_with_payload(payload: serde_json::Value) -> String

作用:为测试快速做一个不真正签名的 JWT 字符串。它方便测试“只读 payload、不验签”的场景。

数据流:输入 JSON payload → 构造固定 header,把 header、payload 和假签名分别 base64url 编码,再用点号拼起来 → 输出 JWT 字符串。

调用关系:它被不带 JWKS 的 JWT 解码测试使用,帮助触发 decode_agent_identity_jwt_payload 这条路径。

调用图:外部调用 2 个(format!, to_vec)。

login/src/auth/agent_identity.rs源码 ↗
domain_logicstartup 和 request handling

这个文件解决的是代理身份的认证问题。程序本地已经有一份 AgentIdentityAuthRecord,里面放着账号、用户、邮箱、套餐、私钥等信息;但光有这些还不够,程序启动后还要向认证服务报到,注册当前这次运行的任务。AgentIdentityAuth::load 就是这个入口:它先决定认证服务地址,默认用正式地址,也允许用环境变量改成测试地址;再用 HTTP 客户端和密钥去调用 register_agent_task,换回 process_task_id。之后,AgentIdentityAuth 像一个“带通行证的钱包”,一边保存原始登录记录,一边提供很多只读小接口,让别的代码拿账号 ID、用户 ID、任务 ID、是否 FedRAMP 账号等信息去加请求头。文件底部的测试专门确认认证服务地址的选择规则,EnvVarGuard 则像临时改门牌的小工具,测试结束会把环境变量恢复原样,避免污染别的测试。

函数细节15
AgentIdentityAuth::load20–33 ↗
async fn load(record: AgentIdentityAuthRecord) -> std::io::Result<Self>

作用:用保存好的代理身份记录,向认证服务注册当前程序任务,并生成一个可在后续请求中使用的认证对象。别人会在把代理身份 JWT 转成本地登录状态时调用它。

数据流:进去的是 AgentIdentityAuthRecord,也就是本地保存的账号信息和私钥。它先读取认证服务地址,再把记录里的运行 ID 和私钥整理成 AgentIdentityKey,配上 HTTP 客户端交给 register_agent_task;成功后得到 process_task_id,并和原记录一起装进 AgentIdentityAuth 返回;失败时把错误转成 std::io::Error 传出去。

调用关系:它由 from_agent_identity_jwt 调用,是代理身份认证对象真正成形的地方。它自己会请 agent_identity_authapi_base_url 决定要连哪个认证服务,请 key 从记录里取出注册任务需要的密钥材料,请 build_reqwest_client 建 HTTP 客户端,最后把网络注册交给外部的 register_agent_task。

调用图:调用 3 个内部函数(agent_identity_authapi_base_url, key, build_reqwest_client);被 1 处调用(from_agent_identity_jwt);外部调用 1 个(register_agent_task)。

AgentIdentityAuth::record35–37 ↗
fn record(&self) -> &AgentIdentityAuthRecord

作用:把内部保存的完整认证记录借给调用者看。它适合那些需要原始登录资料、但不应该修改它的地方使用。

数据流:进去的是这个 AgentIdentityAuth 自己。它不复制、不改动数据,只返回内部 AgentIdentityAuthRecord 的只读引用;出来的是调用者可以读取的记录视图。

调用关系:add_auth_headers 会用它来拿更完整的认证信息,用来拼请求头。它是一个简单的取物窗口,不再调用别的函数。

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

AgentIdentityAuth::process_task_id39–41 ↗
fn process_task_id(&self) -> &str

作用:取出当前进程在认证服务那里登记到的任务编号。这个编号用来告诉服务器:这次请求来自哪一次正在运行的代理任务。

数据流:进去的是 AgentIdentityAuth。它从内部字段 process_task_id 里取出字符串切片返回,不修改任何东西。

调用关系:add_auth_headers 会调用它,把任务编号放进对外请求的认证头里。这个值最早是在 AgentIdentityAuth::load 注册任务时拿到的。

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

AgentIdentityAuth::account_id43–45 ↗
fn account_id(&self) -> &str

作用:取出这个代理身份所属的账号 ID。服务器用它来识别请求归属于哪个账号。

数据流:进去的是 AgentIdentityAuth。它读取内部 record.account_id,并返回只读字符串;对象本身不变。

调用关系:add_auth_headers 会调用它,把账号身份写进请求头。它只负责取值,不负责判断或联网。

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

AgentIdentityAuth::chatgpt_user_id47–49 ↗
fn chatgpt_user_id(&self) -> &str

作用:取出 ChatGPT 用户 ID,也就是这个身份对应的具体用户标识。需要区分同一账号下不同用户时会用到它。

数据流:进去的是 AgentIdentityAuth。它读取内部 record.chatgpt_user_id,返回只读字符串,不产生额外副作用。

调用关系:它是给外部代码读取用户信息的小接口。当前调用图里没有标出直接调用者,但它和 account_id、email 这类方法一样,都是认证对象对外暴露身份资料的出口。

AgentIdentityAuth::email51–53 ↗
fn email(&self) -> &str

作用:取出这个身份对应的邮箱地址。它通常用于显示、日志或需要人类可读用户信息的地方。

数据流:进去的是 AgentIdentityAuth。它直接读取内部 record.email,并返回只读字符串;不会改认证状态。

调用关系:它是认证对象的资料读取口。当前调用图里没有标出直接调用者,但它让其他模块不用知道 record 的内部结构也能拿到邮箱。

AgentIdentityAuth::plan_type55–57 ↗
fn plan_type(&self) -> AccountPlanType

作用:取出账号套餐类型,比如不同账号计划可能对应不同能力或限制。调用者可以据此决定后续行为。

数据流:进去的是 AgentIdentityAuth。它读取 record.plan_type 并按值返回套餐枚举;不访问网络,也不修改记录。

调用关系:它把存储层里的套餐信息包装成认证对象的公开接口。当前调用图里没有标出直接调用者,但它供需要知道账号权益的代码使用。

AgentIdentityAuth::is_fedramp_account59–61 ↗
fn is_fedramp_account(&self) -> bool

作用:判断这个账号是不是 FedRAMP 账号。FedRAMP 可以简单理解为美国政府相关的安全合规要求,通常会影响请求该怎么走、该带哪些标记。

数据流:进去的是 AgentIdentityAuth。它读取 record.chatgpt_account_is_fedramp 这个布尔值,返回 true 或 false;不会改变任何数据。

调用关系:add_auth_headers 会调用它,决定请求头里是否需要体现 FedRAMP 账号属性。它只给出判断结果,具体怎么加头由 add_auth_headers 做。

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

agent_identity_authapi_base_url64–70 ↗
fn agent_identity_authapi_base_url() -> String

作用:决定代理身份认证服务的基础地址。默认走正式 OpenAI 认证地址,但测试或特殊部署时可以用环境变量覆盖。

数据流:它读取环境变量 CODEX_AGENT_IDENTITY_AUTHAPI_BASE_URL。若读到非空值,就去掉前后空白和末尾多余的斜杠后返回;如果没设置、为空或处理后为空,就返回正式生产地址。

调用关系:AgentIdentityAuth::load 在注册任务前会调用它,这样 load 不用自己关心地址从哪里来。测试函数会专门验证它在“有环境变量”和“没环境变量”两种情况下的表现。

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

key72–77 ↗
fn key(record: &AgentIdentityAuthRecord) -> AgentIdentityKey<'_>

作用:把保存记录里的代理运行 ID 和私钥,整理成注册任务接口需要的密钥对象。它像是把散放的证件材料装进规定格式的信封。

数据流:进去的是 AgentIdentityAuthRecord 的只读引用。它取出 agent_runtime_id 和 agent_private_key,放进 AgentIdentityKey;出来的是一个借用原记录内容的密钥结构,不复制或修改原记录。

调用关系:AgentIdentityAuth::load 会调用它,然后把结果交给 register_agent_task。它夹在本地存储格式和外部认证库需要的格式之间,负责做轻量转换。

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

tests::agent_identity_authapi_base_url_prefers_env_value86–95 ↗
fn agent_identity_authapi_base_url_prefers_env_value()

作用:确认只要设置了认证服务地址环境变量,代码就优先使用这个地址,而不是默认正式地址。这样测试环境或私有部署不会误连生产服务。

数据流:它先用 EnvVarGuard::set 临时设置环境变量为一个带末尾斜杠的测试地址。然后调用 agent_identity_authapi_base_url,检查返回值已经去掉末尾斜杠,并且等于期望的测试地址;测试结束时 guard 会自动恢复原环境变量。

调用关系:这是 agent_identity_authapi_base_url 的行为测试。它依赖 EnvVarGuard::set 安全地改环境变量,并用断言确认地址选择规则没有被改坏。

调用图:调用 1 个内部函数(set);外部调用 1 个(assert_eq!)。

tests::agent_identity_authapi_base_url_uses_prod_authapi_by_default99–105 ↗
fn agent_identity_authapi_base_url_uses_prod_authapi_by_default()

作用:确认没有设置环境变量时,代码会使用正式认证服务地址。这样正常用户运行时不需要额外配置也能连到正确服务。

数据流:它先用 EnvVarGuard::remove 临时移除相关环境变量。然后调用 agent_identity_authapi_base_url,检查返回值等于生产地址常量;测试结束时 guard 自动把环境变量恢复到测试前的状态。

调用关系:这是 agent_identity_authapi_base_url 的默认路径测试。它和另一个环境变量测试配套,分别覆盖“有覆盖值”和“无覆盖值”两条路。

调用图:外部调用 2 个(remove, assert_eq!)。

tests::EnvVarGuard::set113–119 ↗
fn set(key: &'static str, value: &str) -> Self

作用:在测试里临时设置一个环境变量,并记住它原来的值,方便测试结束后恢复。它避免一个测试改了全局环境后影响别的测试。

数据流:进去的是环境变量名和要设置的新值。它先读取原值保存起来,再把环境变量改成新值,最后返回 EnvVarGuard;这个 guard 里带着变量名和原值。

调用关系:tests::agent_identity_authapi_base_url_prefers_env_value 会用它布置测试现场。后续恢复工作不由测试手写,而是交给 tests::EnvVarGuard::drop 在 guard 离开作用域时自动完成。

调用图:外部调用 2 个(set_var, var_os)。

tests::EnvVarGuard::remove121–127 ↗
fn remove(key: &'static str) -> Self

作用:在测试里临时删除一个环境变量,并记住它之前有没有值。这样可以可靠地测试“环境变量不存在”的情况。

数据流:进去的是环境变量名。它先读取并保存原值,然后移除这个环境变量,最后返回 EnvVarGuard;guard 负责以后把现场恢复。

调用关系:tests::agent_identity_authapi_base_url_uses_prod_authapi_by_default 会用它制造“没有配置”的场景。测试结束后的恢复由 tests::EnvVarGuard::drop 自动处理。

调用图:外部调用 2 个(remove_var, var_os)。

tests::EnvVarGuard::drop131–138 ↗
fn drop(&mut self)

作用:在 EnvVarGuard 生命周期结束时,把被测试改过的环境变量恢复成原样。它相当于测试现场的“自动清洁工”。

数据流:进去的是即将被销毁的 EnvVarGuard。它查看保存的 original:如果原来有值,就把环境变量设回那个值;如果原来没有值,就把环境变量删除;没有返回值,但会改回进程环境变量。

调用关系:它不是被测试代码显式调用的,而是 Rust 在 EnvVarGuard 离开作用域时自动调用。EnvVarGuard::set 和 EnvVarGuard::remove 负责记录现场,它负责收尾,保证两个环境变量测试不会互相干扰。

调用图:外部调用 2 个(remove_var, set_var)。

login/src/auth/external_bearer.rs源码 ↗
domain_logic认证解析和令牌刷新时活跃;通常发生在发起模型请求前,或认证失效后重试刷新时

这个文件像一个“代取门禁卡的小助手”。系统需要访问外部模型服务时,可能要先拿到一串访问令牌。这里的 BearerTokenRefresher 会按配置运行一个外部程序,让那个程序把令牌打印出来,然后把令牌交给后续请求使用。为了避免每次请求都跑一次外部程序,它会把刚拿到的令牌缓存起来,并记录获取时间;如果配置了刷新间隔,没到时间就继续用旧令牌。这里用到互斥锁(一把锁,防止多个异步任务同时改同一份缓存)来保护缓存,避免并发时重复刷新或把数据写乱。它也会处理很多容易出错的情况:外部程序找不到、运行超时、退出失败、输出不是正常文字、输出为空,都会变成清楚的错误信息。

函数细节8
BearerTokenRefresher::new23–27 ↗
fn new(config: ModelProviderAuthInfo) -> Self

作用:创建一个新的外部 Bearer 令牌刷新器。调用者把“用哪个命令取令牌、在哪里运行、超时时间”等配置交给它,它就准备好后面按需取令牌。

数据流:进去的是 ModelProviderAuthInfo 配置 → 它把配置放进 ExternalBearerAuthState,并用 Arc(一种可共享的引用,让多个地方能安全拿到同一份状态)包起来 → 出来的是 BearerTokenRefresher,里面带着空的令牌缓存。

调用关系:它会调用 ExternalBearerAuthState::new 来建内部状态。调用图里它被 external_bearer_only 使用,说明系统在配置为“只用外部 Bearer 令牌”的认证方式时,会先通过它搭好刷新器。

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

BearerTokenRefresher::auth_mode73–75 ↗
fn auth_mode(&self) -> AuthMode

作用:告诉认证管理器:这个刷新器最终提供的是类似 API Key 的认证材料。这里返回 AuthMode::ApiKey,是为了让上层用统一方式处理认证类型。

数据流:没有读取外部输入 → 直接选择固定的认证模式 ApiKey → 返回给认证框架,后续框架就知道该按 API Key 类方式使用这些令牌。

调用关系:这是 BearerTokenRefresher 实现 ExternalAuth 接口的一部分。认证管理器会在需要判断认证方式时调用它;它本身不再把工作交给其他函数。

BearerTokenRefresher::resolve77–79 ↗
fn resolve(&self) -> ExternalAuthFuture<'_, Option<ExternalAuthTokens>>

作用:在真正发请求前,尽量拿到一个可用令牌。它会优先用缓存,只有缓存没有或过期时,才运行外部命令重新取。

数据流:进去的是刷新器自身保存的配置和缓存 → 它先用互斥锁检查 cached_token;如果缓存还没过期,就把缓存里的字符串包装成 ExternalAuthTokens 返回;如果没有缓存或需要刷新,就调用 run_provider_auth_command 运行外部命令,再把新令牌和当前时间存进缓存 → 出来的是可能存在的一组认证令牌,或者运行命令时产生的错误。

调用关系:这是 ExternalAuth 接口里的“解析认证信息”入口之一,调用图显示它会交给 run_provider_auth_command 真正取令牌,并用 access_token_only 把纯访问令牌包装成统一格式。它被上层认证流程在需要初次拿令牌时调用。

调用图:调用 2 个内部函数(run_provider_auth_command, access_token_only);外部调用 2 个(pin, now)。

BearerTokenRefresher::refresh81–86 ↗
fn refresh(
        &self,
        context: ExternalAuthRefreshContext,
    ) -> ExternalAuthFuture<'_, ExternalAuthTokens>

作用:强制重新拿一次令牌,不管缓存里有没有旧令牌。通常用于上层发现认证失败、需要换一张新“门禁卡”的时候。

数据流:进去的是刷新上下文和刷新器内部配置;这个函数当前不使用上下文内容 → 它直接调用 run_provider_auth_command 获取新令牌,然后锁住缓存,把旧令牌替换成新令牌并记录当前时间 → 出来的是新的 ExternalAuthTokens,或者外部命令失败时的错误。

调用关系:这是 ExternalAuth 接口里的“刷新认证信息”入口。上层认证管理器在令牌失效或需要主动刷新时会调用它;它把实际取令牌的活交给 run_provider_auth_command,再把结果包装成统一令牌对象。

调用图:调用 2 个内部函数(run_provider_auth_command, access_token_only);外部调用 2 个(pin, now)。

BearerTokenRefresher::fmt90–93 ↗
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result

作用:给 BearerTokenRefresher 提供调试打印方式。它故意不把令牌或敏感配置打印出来,避免日志里泄露秘密。

数据流:进去的是格式化器 → 它只写出结构名 BearerTokenRefresher,并标记内容没有完全展开 → 出来的是调试文本,不包含访问令牌。

调用关系:当日志、调试器或错误信息需要显示这个对象时会用到它。它调用标准的 debug_struct 来生成安全的调试外壳,不参与取令牌流程。

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

ExternalBearerAuthState::new102–107 ↗
fn new(config: ModelProviderAuthInfo) -> Self

作用:创建刷新器内部真正保存状态的地方。它把配置保存下来,并准备一个一开始为空的令牌缓存。

数据流:进去的是 ModelProviderAuthInfo 配置 → 它原样保存配置,同时创建 Mutex<Option<CachedExternalBearerToken>>,也就是一个受锁保护、可能为空的缓存槽 → 出来的是 ExternalBearerAuthState。

调用关系:BearerTokenRefresher::new 会调用它。它是内部零件,外部调用者一般不会直接碰它;后续 resolve 和 refresh 都会读取这里的配置和缓存。

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

run_provider_auth_command115–171 ↗
async fn run_provider_auth_command(config: &ModelProviderAuthInfo) -> io::Result<String>

作用:真正运行外部取令牌命令,并从命令输出里读出访问令牌。它是这个文件里最像“去柜台取钥匙”的函数。

数据流:进去的是认证配置,包括命令名、参数、工作目录和超时时间 → 它先调用 resolve_provider_auth_program 算出应该运行哪个程序;然后启动子进程,关闭标准输入,收集标准输出和标准错误,并设置超时;如果程序启动失败、超时、退出码失败、输出不是 UTF-8 文本或输出为空,就返回清楚的错误 → 成功时,把标准输出去掉前后空白后作为 access token 字符串返回。

调用关系:BearerTokenRefresher::resolve 在缓存未命中时会调用它,BearerTokenRefresher::refresh 在强制刷新时也会调用它。它自己会先交给 resolve_provider_auth_program 处理命令路径,然后负责和操作系统子进程打交道。

调用图:调用 1 个内部函数(resolve_provider_auth_program);被 2 处调用(refresh, resolve);外部调用 10 个(null, piped, from_utf8, from_utf8_lossy, new, new, other, timeout, format!, timeout)。

resolve_provider_auth_program173–184 ↗
fn resolve_provider_auth_program(command: &str, cwd: &Path) -> io::Result<PathBuf>

作用:把配置里的命令名变成实际要运行的程序路径。这样配置既可以写绝对路径,也可以写相对当前工作目录的脚本路径,还可以只写一个让系统去 PATH 里找的命令名。

数据流:进去的是 command 字符串和 cwd 工作目录 → 如果 command 已经是绝对路径,就直接使用;如果 command 看起来带目录层级,比如 scripts/get-token,就拼到 cwd 后面;如果只是一个简单命令名,比如 get-token,就原样返回,让系统按环境路径查找 → 出来的是 PathBuf,也就是准备交给子进程启动器的路径。

调用关系:run_provider_auth_command 在启动外部程序前会调用它。它不负责执行命令,只负责把“用户写的命令”解释成“系统该尝试运行的路径”。

调用图:被 1 处调用(run_provider_auth_command);外部调用 3 个(join, new, from)。

model-provider/src/bearer_auth_provider.rs源码 ↗
io_transportrequest handling

发模型请求时,服务端需要知道“你是谁、用哪个账号、是不是特殊合规环境”。这个文件里的 BearerAuthProvider 就像一张出门证件夹:里面可以放访问令牌、账号 ID,以及是否属于 FedRAMP 账号的标记。Bearer token 是一种常见登录方式,意思是请求头里写上“Bearer 加令牌”,服务端看到后就知道这是已授权请求。它实现了 AuthProvider,也就是“会给请求加认证信息的东西”。真正发请求前,add_auth_headers 会检查这些信息是否存在,格式是否能放进 HTTP 头;能放就加入 Authorization、ChatGPT-Account-ID、X-OpenAI-Fedramp。这样调用方不用到处手写认证头,避免漏加、写错或把特殊账号路由错地方。文件底部的测试则确认这些头确实会被加上。

函数细节6
BearerAuthProvider::new14–20 ↗
fn new(token: String) -> Self

作用:用一个访问令牌快速做出一个认证提供者。有人只知道 token、还没有账号 ID 或特殊合规标记时,就用它来生成默认配置。

数据流:进去的是一段 token 字符串 → 它把 token 放进结构体里,把 account_id 设为空,把 is_fedramp_account 设为 false → 出来的是一个可以给请求加 Authorization 头的 BearerAuthProvider。

调用关系:它是正常业务里创建认证对象的入口之一。调用图里显示 bearer_auth_for_provider 会用它,说明上层在为某个模型服务准备认证方式时,会先通过这里把 token 包装成统一的认证提供者。

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

BearerAuthProvider::for_test22–28 ↗
fn for_test(token: Option<&str>, account_id: Option<&str>) -> Self

作用:专门给测试用的便捷构造函数。测试可以自由传入“有或没有 token”“有或没有账号 ID”,不用每次手动拼完整结构体。

数据流:进去的是可选的 token 和可选的 account_id,类型是可能为空的字符串引用 → 它把存在的值转成真正拥有的 String,不存在就保持为空,并默认不是 FedRAMP 账号 → 出来的是一个适合测试各种认证头组合的 BearerAuthProvider。

调用关系:它主要服务测试场景。调用图里显示 auth_request_telemetry_context_tracks_attached_auth_and_retry_phase 和 bearer_auth_provider_adds_auth_headers 会用它,用来搭出可控的认证对象,再检查后续逻辑是否按预期工作。

调用图:被 2 处调用(auth_request_telemetry_context_tracks_attached_auth_and_retry_phase, bearer_auth_provider_adds_auth_headers)。

BearerAuthProvider::add_auth_headers32–46 ↗
fn add_auth_headers(&self, headers: &mut HeaderMap)

作用:把认证信息真正写进 HTTP 请求头里。没有它,请求可能带不上登录凭证,服务端就可能拒绝访问,或者无法识别账号和合规路由。

数据流:进去的是它自己保存的 token、account_id、is_fedramp_account,以及一份可修改的 HeaderMap(HTTP 请求头集合)→ 它先把 token 包成“Bearer xxx”并尝试变成合法头值,成功就写入 Authorization;再把账号 ID 写入 ChatGPT-Account-ID;如果是 FedRAMP 账号,就写入 X-OpenAI-Fedramp: true → 出来时没有单独返回值,但传入的 headers 被补上了这些认证和路由信息;如果某个值格式不合法,就不会硬塞进去。

调用关系:这是 BearerAuthProvider 实现 AuthProvider 接口的核心动作,也就是发请求流程真正需要认证头时会调用的方法。它内部把格式化字符串、生成 HeaderValue、插入 HeaderMap 这些底层小动作串起来;测试会直接调用它,确认请求头最后长成正确样子。

调用图:外部调用 4 个(insert, from_static, from_str, format!)。

tests::bearer_auth_provider_reports_when_auth_header_will_attach55–69 ↗
fn bearer_auth_provider_reports_when_auth_header_will_attach()

作用:这个测试确认:只要认证对象里有 token,系统的认证头遥测就会报告“会附加 Authorization 头”。遥测可以理解成给系统自己看的记录,用来知道请求有没有带认证。

数据流:进去的是测试里手动构造的 BearerAuthProvider,里面有 access-token,没有账号 ID,也不是 FedRAMP 账号 → 测试把它交给认证头遥测检查,再和期望结果比较 → 结果应当显示 attached 为 true,头名是 authorization;测试本身不改业务数据,只验证判断是否正确。

调用关系:它位于测试模块里,用来守住“有 token 就会被认为能加认证头”这条规则。调用图里显示它使用断言比较结果;如果以后认证提供者的行为被改坏,这个测试会失败并提醒开发者。

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

tests::bearer_auth_provider_adds_auth_headers72–90 ↗
fn bearer_auth_provider_adds_auth_headers()

作用:这个测试确认普通账号请求会同时加上登录 token 和账号 ID。它防止以后改代码时漏掉 Authorization 或 ChatGPT-Account-ID 其中一个。

数据流:进去的是通过 BearerAuthProvider::for_test 做出的测试认证对象,里面有 access-token 和 workspace-123,还有一份空的 HeaderMap → 测试调用 add_auth_headers,让它往空请求头里写内容 → 出来时 HeaderMap 应该包含 Authorization: Bearer access-token 和 ChatGPT-Account-ID: workspace-123,然后用断言检查它们。

调用关系:它是 add_auth_headers 的直接验收用例。调用图里显示它会用 for_test 准备数据,再用断言检查结果;这样构造函数和加头逻辑能一起被覆盖到。

调用图:调用 1 个内部函数(for_test);外部调用 2 个(new, assert_eq!)。

tests::bearer_auth_provider_adds_fedramp_routing_header_for_fedramp_accounts93–109 ↗
fn bearer_auth_provider_adds_fedramp_routing_header_for_fedramp_accounts()

作用:这个测试确认 FedRAMP 账号会额外带上特殊路由头。FedRAMP 可以简单理解为美国政府相关的合规环境,服务端可能要把这类请求送到特定通道。

数据流:进去的是一个手动构造的 BearerAuthProvider,里面有 token、账号 ID,并把 is_fedramp_account 设为 true,再加上一份空 HeaderMap → 测试调用 add_auth_headers → 出来时 HeaderMap 里应该多出 X-OpenAI-Fedramp: true,测试用断言确认这个标记确实存在。

调用关系:它专门保护 FedRAMP 这条特殊分支。调用图里显示它主要通过断言检查结果;如果将来有人改 add_auth_headers 时忘了这个合规路由头,这个测试会立刻暴露问题。

调用图:外部调用 2 个(new, assert_eq!)。

model-provider/src/auth.rs源码 ↗
domain_logicrequest handling / auth resolution

模型提供商不止一种:有 OpenAI 这种需要登录身份的,也有本地模型或测试服务这种完全不需要鉴权的。这个文件就像一个“出门前检查证件的门卫”:先看当前模型提供商有没有自己指定的 API Key 或 Bearer Token(Bearer Token 可以理解成一张通行令牌),有就优先用它;没有的话,再看用户当前登录信息;如果也没有,并且这个提供商允许不登录,就什么认证头都不加。它还专门处理 Agent Identity,也就是代理任务自己的身份,会生成一段 Authorization 请求头,让服务端知道“这是哪个代理在执行哪个任务”。同时它会拒绝明显不匹配的认证方式,比如 Bedrock API Key 只能给 Amazon Bedrock 用,不能拿去请求普通 OpenAI 接口。文件底部的测试用例检查了两个关键场景:本地服务不加认证头,以及 OpenAI 提供商会拒绝 Bedrock API Key。

函数细节9
AgentIdentityAuthProvider::add_auth_headers26–53 ↗
fn add_auth_headers(&self, headers: &mut HeaderMap)

作用:把“代理身份”的认证信息加到 HTTP 请求头里。HTTP 请求头可以理解成随请求一起递交的小纸条,用来告诉服务器“我是谁、我要执行哪个任务”。

数据流:进去的是一个可修改的请求头集合,以及这个对象里保存的代理身份信息。它先读取代理运行时编号、私钥、任务编号,生成 Authorization 认证头;成功生成后写进请求头。然后再写入账号 ID;如果这是 FedRAMP 账号,也就是美国政府合规环境账号,还会额外写入一个标记。出来的是同一个请求头集合,但里面多了认证相关字段;如果某个字段格式不合法,它会跳过,不会让程序崩掉。

调用关系:它是 AgentIdentityAuthProvider 这个认证提供者真正干活的地方。auth_provider_from_auth 遇到 CodexAuth::AgentIdentity 时会创建这个提供者;之后外层发请求前会调用它,把代理身份变成服务器能看懂的请求头。

调用图:调用 4 个内部函数(account_id, is_fedramp_account, process_task_id, record);外部调用 4 个(insert, from_static, from_str, authorization_header_for_agent_task)。

UnauthenticatedAuthProvider::add_auth_headers62–62 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:明确表示“这个请求不需要认证”,所以什么请求头都不加。它主要给本地模型、开源模型服务或测试服务使用。

数据流:进去的是一个可修改的请求头集合。它不读取任何登录信息,也不写任何字段。出来的请求头和进去时一样,保持空白或原样。

调用关系:unauthenticated_auth_provider 会创建这个提供者;resolve_provider_auth 在没有登录信息、且提供商不要求 OpenAI 鉴权时会返回它。它让“不登录也可以访问”的情况变成一个明确的对象,而不是到处用特殊判断。

unauthenticated_auth_provider65–67 ↗
fn unauthenticated_auth_provider() -> SharedAuthProvider

作用:创建一个“不加认证头”的认证提供者。调用方用它来表示:后续发请求时不需要带钥匙。

数据流:进去没有额外参数。它新建 UnauthenticatedAuthProvider,并用 Arc 包起来;Arc 是一种可共享的引用计数指针,可以理解成多人共用同一个小工具且自动记账。出来的是 SharedAuthProvider,供请求代码统一调用。

调用关系:resolve_provider_auth 在判断出当前没有可用认证、也没有提供商专属令牌时会调用它。它把“无需认证”包装成和其他认证方式一样的接口,让后面的请求流程不用关心具体是哪种认证。

调用图:被 1 处调用(resolve_provider_auth);外部调用 1 个(new)。

auth_manager_for_provider72–80 ↗
fn auth_manager_for_provider(
    auth_manager: Option<Arc<AuthManager>>,
    provider: &ModelProviderInfo,
) -> Option<Arc<AuthManager>>

作用:决定某个模型提供商应该使用哪个认证管理器。认证管理器可以理解成保存和刷新凭证的管家。

数据流:进去的是一个可选的基础 AuthManager,以及模型提供商信息。它查看提供商配置里是否写了专门的认证命令;如果有,就创建一个只从外部命令拿 Bearer Token 的管理器;如果没有,就沿用传进来的基础管理器。出来的是一个可选的 AuthManager。

调用关系:它会在创建模型提供商客户端时被调用,用来提前选好认证来源。它不直接给请求加头,而是为后续 resolve_provider_auth 或其他认证流程准备好正确的凭证管道。

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

resolve_provider_auth82–100 ↗
fn resolve_provider_auth(
    auth: Option<&CodexAuth>,
    provider: &ModelProviderInfo,
) -> codex_protocol::error::Result<SharedAuthProvider>

作用:这是本文件的核心选择器:根据“当前登录信息”和“模型提供商配置”,决定最终用哪种认证方式。它保证请求不会带错凭证,也能在允许匿名访问时不带凭证。

数据流:进去的是可选的 CodexAuth 登录快照,以及一个 ModelProviderInfo 提供商配置。它先检查是否把 Bedrock API Key 用在不支持的地方,是的话直接返回错误。然后优先检查提供商自己配置的 API Key 或实验性 Bearer Token;如果找到了,就返回 BearerAuthProvider。否则,如果传入了用户登录信息,就把它转换成合适的认证提供者;如果没有登录信息,就返回不加认证头的提供者。出来的是一个可共享的认证提供者,或者一个明确的错误。

调用关系:这是外层代码在准备请求认证时会走的汇合点。测试 tests::unauthenticated_auth_provider_adds_no_headers 和 tests::openai_provider_rejects_bedrock_api_key_auth 都直接验证它。它会把工作分给 bearer_auth_for_provider、auth_provider_from_auth 或 unauthenticated_auth_provider。

调用图:调用 3 个内部函数(auth_provider_from_auth, bearer_auth_for_provider, unauthenticated_auth_provider);被 2 处调用(openai_provider_rejects_bedrock_api_key_auth, unauthenticated_auth_provider_adds_no_headers);外部调用 3 个(new, matches!, UnsupportedOperation)。

bearer_auth_for_provider102–114 ↗
fn bearer_auth_for_provider(
    provider: &ModelProviderInfo,
) -> codex_protocol::error::Result<Option<BearerAuthProvider>>

作用:检查模型提供商自己有没有直接配置可用的令牌。这个令牌会优先于用户登录信息使用。

数据流:进去的是模型提供商信息。它先尝试读取 provider.api_key,也就是提供商配置里的 API Key;如果有,就创建 BearerAuthProvider。没有 API Key 时,再看 experimental_bearer_token 这个实验性令牌字段;如果也没有,就返回 None。出来的是“可能存在的 BearerAuthProvider”,或者读取 API Key 时产生的错误。

调用关系:resolve_provider_auth 会先调用它,因为提供商专属凭证优先级更高。它只负责找和包装令牌,不负责决定兜底用登录还是匿名。

调用图:调用 2 个内部函数(api_key, new);被 1 处调用(resolve_provider_auth)。

auth_provider_from_auth117–132 ↗
fn auth_provider_from_auth(auth: &CodexAuth) -> SharedAuthProvider

作用:把 Codex 登录信息转换成真正会给请求加头的认证提供者。换句话说,它把“用户或代理的身份快照”变成“发请求时能用的通行证工具”。

数据流:进去的是一个 CodexAuth。它根据认证种类分流:如果是 AgentIdentity,就创建 AgentIdentityAuthProvider;如果是普通 API Key、ChatGPT 登录、访问令牌等,就取出 token、账号 ID、FedRAMP 标记,包装成 BearerAuthProvider;如果传进来的是 BedrockApiKey,则认为上游本应拦住,所以这里是不可达分支。出来的是可共享的认证提供者。

调用关系:resolve_provider_auth 在没有提供商专属令牌、但有用户登录信息时会调用它。它把不同登录方式统一成 AuthProvider 接口,让后续发请求的代码只管调用 add_auth_headers。

调用图:调用 3 个内部函数(get_account_id, get_token, is_fedramp_account);被 1 处调用(resolve_provider_auth);外部调用 3 个(new, clone, unreachable!)。

tests::unauthenticated_auth_provider_adds_no_headers144–150 ↗
fn unauthenticated_auth_provider_adds_no_headers()

作用:验证本地或开源模型提供商在没有登录信息时,不会偷偷加认证头。这可以防止把不该发的密钥发到本地服务或测试服务。

数据流:进去没有外部输入。它创建一个指向 localhost 的开源模型提供商,然后用 None 作为认证信息调用 resolve_provider_auth。接着把得到的认证提供者转换成请求头,并检查结果为空。出来是测试通过或失败;它不改动生产数据。

调用关系:这是对 resolve_provider_auth 和 unauthenticated_auth_provider 的行为检查。它模拟“没有账号、请求本地模型”的场景,确保 UnauthenticatedAuthProvider::add_auth_headers 确实什么都不写。

调用图:调用 1 个内部函数(resolve_provider_auth);外部调用 2 个(assert!, create_oss_provider_with_base_url)。

tests::openai_provider_rejects_bedrock_api_key_auth153–167 ↗
fn openai_provider_rejects_bedrock_api_key_auth()

作用:验证 OpenAI 提供商不会接受 Bedrock API Key。这个测试防止用户把不同平台的钥匙混用,导致难懂的认证错误或安全问题。

数据流:进去没有外部输入。它创建一个 OpenAI 提供商,再构造一个 BedrockApiKey 登录信息,然后调用 resolve_provider_auth。它期望返回 UnsupportedOperation 错误,并检查错误消息正好是预设的提示;如果返回了别的错误或成功结果,测试就失败。

调用关系:这是对 resolve_provider_auth 开头那道“错误钥匙拦截”逻辑的检查。它确保 Bedrock 专用认证不会流到 auth_provider_from_auth 或后续请求发送流程里。

调用图:调用 2 个内部函数(create_openai_provider, resolve_provider_auth);外部调用 3 个(assert_eq!, BedrockApiKey, panic!)。

Bedrock 与 AWS 签名

在 AWS 配置加载和 SigV4 请求签名之上,实现 Bedrock 专用端点解析和认证选择。

aws-auth/src/config.rs源码 ↗
configconfig load

这个文件像是 AWS 认证流程的“出发前检查员”。程序要访问 AWS,光有一堆文字配置还不够,还得让 AWS SDK(AWS 官方提供的一套访问工具)知道用哪个账号资料、哪个地区、以及怎样找登录凭证。这里先检查服务名是不是空的,因为 AWS 签名必须知道请求的是哪个服务;然后按用户设置选择 profile(可以理解成 AWS 本地配置里的账号档案)和 region(AWS 地区,比如某个数据中心区域),再让 SDK 自己去加载完整配置。加载完后,它还提供两个小函数:一个取出凭证提供器,也就是“去哪拿账号钥匙”的来源;另一个取出最终确定的地区。这样后面的认证代码不用关心 AWS SDK 的复杂加载过程,只拿到已经整理好的结果。

函数细节3
load_sdk_config9–23 ↗
async fn load_sdk_config(config: &AwsAuthConfig) -> Result<SdkConfig, AwsAuthError>

作用:这个函数把项目自己的 AWS 配置转换成 AWS SDK 的正式配置。它会先拦住空的服务名,因为没有服务名就没法正确给 AWS 请求做签名。

数据流:进去的是一个 AwsAuthConfig,里面可能有服务名、profile 和 region。函数先检查 service 去掉空格后是不是空;如果空,就返回 EmptyService 错误。否则它创建一个 AWS SDK 配置加载器,按需塞入 profile 和 region,然后异步加载,最后出来一个 SdkConfig;它不直接改原配置,只返回加载好的 SDK 配置或错误。

调用关系:它由更上层的 load 调用,通常是在认证配置初始化时使用。它自己把具体加载工作交给 AWS SDK 的 defaults、latest 和 Region::new 等外部工具,完成后把可用的 SdkConfig 交回给 load,让后续步骤继续取凭证和地区。

调用图:被 1 处调用(load);外部调用 3 个(latest, new, defaults)。

credentials_provider25–31 ↗
fn credentials_provider(
    sdk_config: &SdkConfig,
) -> Result<SharedCredentialsProvider, AwsAuthError>

作用:这个函数从已经加载好的 AWS SDK 配置里取出“凭证提供器”。凭证提供器可以理解成一个取钥匙的人,后面需要它来拿到访问 AWS 的身份信息。

数据流:进去的是 SdkConfig。函数向 SDK 配置询问有没有 credentials_provider;如果有,就把它作为 SharedCredentialsProvider 返回;如果没有,就返回 MissingCredentialsProvider 错误。它只读取配置,不修改任何东西。

调用关系:它由 load 在 SDK 配置已经加载完成后调用。它不自己寻找凭证,而是相信 AWS SDK 已经按环境变量、本地配置或其他方式整理好了来源;它只负责把这个来源取出来,供后面的认证流程使用。

调用图:被 1 处调用(load);外部调用 1 个(credentials_provider)。

resolved_region33–38 ↗
fn resolved_region(sdk_config: &SdkConfig) -> Result<String, AwsAuthError>

作用:这个函数从 AWS SDK 配置里取出最终确定的 AWS 地区,并转换成普通字符串。后面签名或访问 AWS 时必须知道地区,否则请求可能发错地方或签名不合法。

数据流:进去的是 SdkConfig。函数读取其中的 region;如果读到了,就把地区转换成 String 返回;如果没有地区,就返回 MissingRegion 错误。它不会推断新地区,也不会修改配置,只负责确认结果存在并拿出来。

调用关系:它由 load 在配置加载完成后调用,用来确认 AWS SDK 最终解析出了地区。它把读取地区这件事交给 SDK 配置的 region 方法,拿到结果后交回给 load,让认证流程继续使用这个明确的地区。

调用图:被 1 处调用(load);外部调用 1 个(region)。

aws-auth/src/signing.rs源码 ↗
domain_logicrequest handling

AWS 很多接口不能只靠用户名密码访问,而是要求每个请求都带上 SigV4 签名。SigV4 是 AWS 的第四版签名规则,简单说就是把请求方法、网址、请求头、请求正文、时间、地区、服务名和密钥一起算出一组认证信息。这个文件做的事就是:拿到一份还没签名的请求,先把请求头和正文整理成 AWS 签名库能看懂的格式;再用凭证、地区、服务名和时间生成签名参数;然后调用 AWS 官方签名逻辑算出该加哪些头;最后把这些头贴回请求上,返回一份已经带认证信息的请求。它也会把各种可能出错的地方转成项目自己的 AwsAuthError,比如请求头不是合法文本、网址不合法、签名参数拼不出来等。没有它,请求即使格式正确,也会被 AWS 当成“没身份证明”而拒绝。

函数细节2
sign_request17–68 ↗
fn sign_request(
    credentials: &Credentials,
    region: &str,
    service: &str,
    request: AwsRequestToSign,
    time: SystemTime,
) -> Result<AwsSignedRequest, AwsAuthError>

作用:给一条准备发往 AWS 的请求加上 AWS SigV4 签名。调用者会在真正发送请求前用它补齐认证用的请求头,这样 AWS 才能确认请求可信。

数据流:进去的是 AWS 凭证、地区名、服务名、一份待签名请求,以及当前时间。它先把请求头转成可签名的文本形式,把请求正文包装成签名库需要的字节内容;再用凭证、地区、服务名和时间搭好签名参数;然后调用 AWS 签名函数算出应该添加或修改哪些请求头;最后重新组装一个 HTTP 请求,把签名信息应用上去。出来的是 AwsSignedRequest,里面有签名后的网址和请求头;如果中间遇到坏请求头、坏网址或签名失败,就返回 AwsAuthError。

调用关系:它是实际完成签名的核心步骤,由上层的 sign_at 在需要按指定时间签名时调用。它自己不手写复杂的加密算法,而是把请求整理好后交给 aws_sigv4 里的签名工具;签名工具给出修改说明后,它再把这些说明应用到 HTTP 请求上。

调用图:被 1 处调用(sign_at);外部调用 8 个(clone, Bytes, new, default, from_str, sign, builder, builder)。

header_value71–76 ↗
fn header_value(headers: &http::HeaderMap, name: &str) -> Option<String>

作用:这是测试时用的小帮手,用来从一组 HTTP 请求头里取出某个头的文本值。它让测试代码更容易检查签名后有没有加上预期的头。

数据流:进去的是一张请求头表和一个请求头名字。它按名字查找对应的头,如果找到了,并且这个值能安全地当作普通文本读取,就把它转成 String 返回;如果没有这个头,或者这个头不是合法文本,就返回 None。它只读取数据,不修改请求头。

调用关系:这个函数只在测试编译时存在。测试代码通常会先调用签名流程得到结果,再用它从结果的 headers 里取出某个字段来做断言;它内部只是借用 HTTP 头表自己的 get 方法完成查找。

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

aws-auth/src/lib.rs源码 ↗
domain_logicstartup 和 request handling

AWS 的很多接口不能直接裸发 HTTP 请求,必须带上 SigV4 签名。SigV4 可以理解成 AWS 要求的“防伪印章”:它用访问密钥、区域、服务名、时间和请求内容算出一组认证头,AWS 收到后才能确认是谁发的、请求有没有被改过。这个文件提供了三类东西:配置 AwsAuthConfig,表示要用哪个 profile、region 和 AWS 服务;请求和签名结果 AwsRequestToSign、AwsSignedRequest,用来在外部代码和签名器之间传递数据;以及 AwsAuthContext,它像一张已经准备好的“盖章工作台”,先加载凭证和区域,之后可以反复给请求签名。它还统一定义 AwsAuthError,把缺配置、凭证加载失败、URL 不合法、签名失败等问题说清楚,并能判断某些错误是否值得重试。没有这个文件,调用 AWS 接口的代码就要到处自己处理凭证、区域、签名和错误分类,很容易漏签、签错,或者把不可恢复的错误反复重试。

函数细节14
AwsAuthContext::fmt71–76 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:这个函数决定 AwsAuthContext 被打印成调试信息时长什么样。它只展示区域和服务名,不把敏感的凭证内容打印出来,避免日志里泄露密钥。

数据流:进去的是一个 AwsAuthContext 和一个调试输出器 → 它把 region 和 service 写进调试结构里,并刻意不展开 credentials_provider → 出来的是格式化是否成功的结果,外部看到的是安全的调试文本。

调用关系:当开发者或日志系统用调试格式打印 AwsAuthContext 时,Rust 会自动走到这里。它把具体排版交给标准库的 debug_struct,但自己决定哪些字段可以露出、哪些字段必须藏住。

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

AwsAuthContext::load80–90 ↗
async fn load(config: AwsAuthConfig) -> Result<Self, AwsAuthError>

作用:这个函数根据 AwsAuthConfig 准备好一个可以签名的 AwsAuthContext。也就是先把 AWS 凭证、区域和服务名都找齐,之后请求来了就能直接签。

数据流:进去的是 profile、region、service 这些配置 → 它先让 config 模块加载 AWS SDK 配置,再从里面取出凭证提供器和最终区域,同时把服务名去掉首尾空格后保存 → 出来的是可复用的 AwsAuthContext;如果服务名为空、找不到凭证或区域,就返回 AwsAuthError。

调用关系:它通常在启动或选择认证方式时被调用,比如 resolve_auth_method 会用它建立 AWS 认证环境。测试里的 load_rejects_empty_service_name 也会调用它,确认空服务名会被拒绝。它把读取 SDK 配置、取凭证、取区域这些细活交给 config 模块。

调用图:调用 3 个内部函数(credentials_provider, load_sdk_config, resolved_region);被 2 处调用(load_rejects_empty_service_name, resolve_auth_method)。

AwsAuthContext::region92–94 ↗
fn region(&self) -> &str

作用:这个函数把当前认证上下文里使用的 AWS 区域拿出来给别人看。比如外部代码想确认请求会发往哪个区域时会用它。

数据流:进去的是已经加载好的 AwsAuthContext → 它只读取里面保存的 region 字符串 → 出来的是区域文本的只读引用,不会修改任何东西。

调用关系:它是一个很轻的查询口。其他流程在需要展示、记录或按区域做判断时,可以从 AwsAuthContext 这里直接取值,不必重新解析配置。

AwsAuthContext::service96–98 ↗
fn service(&self) -> &str

作用:这个函数返回当前要签名的 AWS 服务名。服务名是 SigV4 签名的一部分,签错服务名,AWS 就不会认这个请求。

数据流:进去的是 AwsAuthContext → 它读取里面保存的 service 字符串 → 出来的是服务名的只读引用,不会触碰凭证或请求内容。

调用关系:它和 region 一样,是给外部查看上下文状态的小窗口。签名本身会在 sign_at 里使用 service,这个函数则方便其他代码了解当前上下文对应哪个 AWS 服务。

AwsAuthContext::sign100–102 ↗
async fn sign(&self, request: AwsRequestToSign) -> Result<AwsSignedRequest, AwsAuthError>

作用:这个函数给一个待发送的 HTTP 请求加上 AWS SigV4 认证信息。调用者不用关心当前时间怎么取,也不用自己拿凭证。

数据流:进去的是 AwsRequestToSign,里面有方法、URL、头和请求体 → 它取当前系统时间,然后把请求和时间交给 sign_at → 出来的是 AwsSignedRequest,包含原 URL 和加好认证头的 headers;失败时返回 AwsAuthError。

调用关系:正常发 AWS 请求时,apply_auth 会调用它。它自己不做具体签名计算,而是补上“现在是什么时间”这个信息,再把真正的工作交给 sign_at。

调用图:调用 1 个内部函数(sign_at);被 1 处调用(apply_auth);外部调用 1 个(now)。

AwsAuthContext::sign_at104–111 ↗
async fn sign_at(
        &self,
        request: AwsRequestToSign,
        time: SystemTime,
    ) -> Result<AwsSignedRequest, AwsAuthError>

作用:这个函数是在指定时间点给请求签名。它主要给内部和测试用,因为固定时间可以让签名结果稳定,方便检查。

数据流:进去的是待签名请求和一个明确的时间 → 它先通过 credentials_provider 异步拿到 AWS 凭证,再把凭证、区域、服务名、请求和时间交给 signing::sign_request → 出来的是签好名的请求部件;如果拿凭证或签名失败,就返回对应错误。

调用关系:AwsAuthContext::sign 会把日常签名请求转到这里。测试也直接调用它并传固定时间,避免测试结果因为当前时间变化而变。真正拼 SigV4 头的细节不在这里,而是在 signing 模块的 sign_request 里。

调用图:调用 1 个内部函数(sign_request);被 1 处调用(sign);外部调用 1 个(provide_credentials)。

AwsAuthError::is_retryable116–133 ↗
fn is_retryable(&self) -> bool

作用:这个函数判断某个 AWS 认证错误值不值得重试。它帮上层代码区分“网络或临时服务抖动”与“配置写错、重试也没用”。

数据流:进去的是一个 AwsAuthError → 它检查错误类型,只有凭证提供器超时或临时失败这类情况会被认为可重试 → 出来的是 true 或 false,不会修改错误本身。

调用关系:上层的 aws_auth_error_to_auth_error 会用它决定错误该怎么包装和处理。它把重试策略集中在一个地方,避免每个调用方都自己猜哪些错误能恢复。

调用图:被 1 处调用(aws_auth_error_to_auth_error);外部调用 1 个(matches!)。

tests::test_context147–159 ↗
fn test_context(session_token: Option<&str>) -> AwsAuthContext

作用:这个测试辅助函数造一个假的 AwsAuthContext,里面放固定的测试密钥。这样测试签名逻辑时不需要真的读取用户电脑上的 AWS 配置。

数据流:进去的是一个可选的 session token,也就是临时凭证里的额外令牌 → 它用固定 access key、secret key 和可选 token 创建凭证提供器,并固定 region 为 us-east-1、service 为 bedrock → 出来的是可直接用于测试签名的 AwsAuthContext。

调用关系:多个签名测试会先调用它得到测试上下文,再调用 sign_at。它把构造假凭证的重复代码收在一起,让每个测试只关注自己要验证的行为。

调用图:外部调用 2 个(new, new)。

tests::test_request161–174 ↗
fn test_request() -> AwsRequestToSign

作用:这个测试辅助函数造一个固定的 HTTP 请求样本。它让测试每次都拿同一份请求去签名,结果才好比较。

数据流:进去没有参数 → 它新建请求头,放入 content-type 和 x-test-header,再设置 POST 方法、Bedrock 示例 URL 和一段 JSON 请求体 → 出来的是 AwsRequestToSign。

调用关系:签名相关测试会调用它生成待签名请求,然后交给 sign_at。它的存在让测试不用每次手写同一批请求字段。

调用图:外部调用 3 个(from_static, new, from_static)。

tests::sign_adds_sigv4_headers_and_preserves_existing_headers177–203 ↗
async fn sign_adds_sigv4_headers_and_preserves_existing_headers()

作用:这个测试确认签名后真的加上了 AWS 需要的认证头,同时不会把原来已有的请求头弄丢。它保护的是“签名要补东西,但不能破坏原请求”。

数据流:进去没有外部输入 → 它用 test_context 造无 session token 的上下文,用 test_request 造请求,并用固定时间调用 sign_at → 然后检查 content-type 和 x-test-header 还在,URL 没变,Authorization 头以 AWS4-HMAC-SHA256 开头,并且有 x-amz-date。

调用关系:它直接覆盖 AwsAuthContext::sign_at 与 signing 模块协作后的结果。test_context 和 test_request 提供稳定材料,断言负责确认签名行为符合预期。

调用图:外部调用 5 个(from_secs, assert!, assert_eq!, test_context, test_request)。

tests::credentials_provider_failures_are_retryable206–215 ↗
fn credentials_provider_failures_are_retryable()

作用:这个测试确认临时性的凭证获取失败会被标记为可重试。比如凭证服务短暂不可用或响应超时,重新试一次可能就好了。

数据流:进去没有外部输入 → 它手动构造 provider_error 和 provider_timed_out 两种凭证错误,再包成 AwsAuthError::Credentials → 出来没有业务返回值,但断言要求 is_retryable 返回 true。

调用关系:它专门保护 AwsAuthError::is_retryable 的判断规则。这样以后有人改错误分类时,不容易把本该重试的临时故障误判成不能重试。

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

tests::deterministic_aws_auth_errors_are_not_retryable218–231 ↗
fn deterministic_aws_auth_errors_are_not_retryable()

作用:这个测试确认确定性的错误不会被重试。确定性错误就是配置空了、profile 写错了、没有可用来源这类“再试一遍也还是错”的问题。

数据流:进去没有外部输入 → 它构造 EmptyService、not_loaded_no_source、invalid_configuration、unhandled 等错误 → 然后逐个检查 is_retryable 都返回 false。

调用关系:它和 credentials_provider_failures_are_retryable 配成一组,分别守住“能重试”和“不能重试”的边界。目标是让上层错误处理不要浪费时间反复请求。

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

tests::sign_includes_session_token_when_credentials_have_one234–247 ↗
async fn sign_includes_session_token_when_credentials_have_one()

作用:这个测试确认如果 AWS 凭证里带有 session token,签名后的请求头也会带上这个令牌。临时凭证少了这个头,AWS 通常会拒绝请求。

数据流:进去没有外部输入 → 它用 test_context 造一个带 session-token 的上下文,用 test_request 造请求,并固定时间签名 → 最后检查签名结果里的 x-amz-security-token 正好是 session-token。

调用关系:它验证 AwsAuthContext::sign_at、凭证对象和 signing::sign_request 之间是否正确传递临时凭证令牌。test_context 负责提供带 token 的测试凭证。

调用图:外部调用 4 个(from_secs, assert_eq!, test_context, test_request)。

tests::load_rejects_empty_service_name250–260 ↗
async fn load_rejects_empty_service_name()

作用:这个测试确认服务名为空时,加载 AWS 认证上下文会失败。因为服务名是 SigV4 签名必需的一部分,空服务名没有合法意义。

数据流:进去没有外部输入 → 它构造一个 service 只有空格的 AwsAuthConfig,并调用 AwsAuthContext::load → 预期出来的是错误,然后检查错误文字是 AWS service name must not be empty。

调用关系:它直接测试 AwsAuthContext::load 对坏配置的防线。这个测试保证认证上下文不会在启动时带着无效服务名继续往后走,避免后面签名阶段才出现更难懂的错误。

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

model-provider/src/amazon_bedrock/auth.rs源码 ↗
io_transportstartup 和 request handling

访问 Amazon Bedrock 不是随便发 HTTP 请求就行,必须带上正确的身份凭证。这个文件就像进门前的安检台:先看用户有没有已经配置好的 Bedrock API key;没有的话,再看系统环境变量里有没有 AWS_BEARER_TOKEN_BEDROCK;还没有,就走 AWS SDK 那套凭证流程,并给请求做 SigV4 签名。SigV4 可以理解成 AWS 要求的一种“防伪签名”,会把请求方法、网址、请求头和正文一起算进去,证明请求没被篡改。文件里还有一个很重要的小修补:Bedrock Mantle 前门不会保留带下划线的旧式请求头,所以签名前会先删掉这类头,避免“签的时候有、AWS 验的时候没”导致认证失败。整体上,它把复杂的 AWS 身份验证细节包起来,让上层只拿到一个能给请求加认证信息的 AuthProvider。

函数细节16
resolve_auth_method33–54 ↗
async fn resolve_auth_method(
    managed_auth: Option<&BedrockApiKeyAuth>,
    aws: &ModelProviderAwsAuthInfo,
) -> Result<BedrockAuthMethod>

作用:决定这次连接 Amazon Bedrock 到底用哪种认证方式。它按优先级检查:先用项目里托管的 API key,再用环境变量里的 bearer token,最后才走 AWS SDK 凭证。

数据流:进去的是可选的托管 Bedrock 登录信息,以及模型提供商里的 AWS 配置;它先读这些配置和环境变量,必要时调用 aws_auth_config 生成 AWS 登录配置并让 AwsAuthContext::load 加载凭证;出来的是一个 BedrockAuthMethod,里面要么装着 token 和 region,要么装着可以给请求签名的 AWS 上下文。

调用关系:这是认证选择的第一道分流口。resolve_provider_auth 会调用它来创建真正的认证提供者,其他地方的 resolve_region 也会借它判断区域信息;它自己会把读取环境变量、解析区域、加载 AWS 凭证这些小步骤交给 non_empty_env_var_from、bearer_token_region、aws_auth_config 和 load。

调用图:调用 4 个内部函数(load, bearer_token_region, non_empty_env_var_from, aws_auth_config);被 2 处调用(resolve_provider_auth, resolve_region)。

resolve_provider_auth56–71 ↗
async fn resolve_provider_auth(
    managed_auth: Option<&BedrockApiKeyAuth>,
    aws: &ModelProviderAwsAuthInfo,
) -> Result<SharedAuthProvider>

作用:把“选出来的认证方式”包装成上层统一能用的 AuthProvider。这样后面的请求发送代码不用关心底层到底是 bearer token,还是 AWS SigV4 签名。

数据流:进去的是托管登录信息和 AWS 配置;它先调用 resolve_auth_method 得到具体认证方式;如果是 token,就创建 BearerAuthProvider;如果是 AWS SDK 凭证,就创建 BedrockMantleSigV4AuthProvider;出来的是一个共享的认证提供者 SharedAuthProvider。

调用关系:它是外部最常用的入口之一,把底层分支藏起来。它会先问 resolve_auth_method 该走哪条路,然后把结果交给 BearerAuthProvider 或 BedrockMantleSigV4AuthProvider::new,最后交给请求发送流程使用。

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

non_empty_env_var_from73–81 ↗
fn non_empty_env_var_from(
    name: &'static str,
    env_var: impl Fn(&'static str) -> std::result::Result<String, std::env::VarError>,
) -> Option<String>

作用:安全地读取一个环境变量,并且把空字符串当成“没设置”。这能避免用户设置了空值却被误认为有有效凭证。

数据流:进去的是环境变量名字,以及一个读取环境变量的函数;它读取值、去掉前后空格,再检查是不是空;出来的是 Some(干净的字符串) 或 None。

调用关系:它是一个小工具,主要被 resolve_auth_method 用来找 AWS_BEARER_TOKEN_BEDROCK,也被 bearer_token_region 间接用于查 AWS_REGION 和 AWS_DEFAULT_REGION。测试里通过传入假的读取函数来模拟不同环境。

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

bearer_token_region83–97 ↗
fn bearer_token_region(
    aws: &ModelProviderAwsAuthInfo,
    env_var: impl Fn(&'static str) -> std::result::Result<String, std::env::VarError> + Copy,
) -> Result<String>

作用:给 bearer token 认证找出 AWS 区域。Bedrock 的 token 不能只知道 token,还必须知道请求发往哪个 AWS 区域。

数据流:进去的是模型提供商的 AWS 配置,以及读取环境变量的函数;它先看配置里的 region,再看 AWS_REGION,最后看 AWS_DEFAULT_REGION;出来的是去掉空格后的区域字符串。如果三个地方都没有,就返回一个致命错误,告诉用户必须配置区域。

调用关系:resolve_auth_method 在发现使用 bearer token 时会调用它。多个测试也直接调用它,确认区域优先级是“配置优先,然后 AWS_REGION,最后 AWS_DEFAULT_REGION”,并确认缺失时会给出清楚的错误。

调用图:调用 1 个内部函数(region_from_config);被 5 处调用(resolve_auth_method, bedrock_bearer_auth_prefers_configured_region_and_uses_header, bedrock_bearer_auth_rejects_missing_configured_region, bedrock_bearer_auth_uses_aws_default_region_env, bedrock_bearer_auth_uses_aws_region_env)。

aws_auth_error_to_codex_error99–101 ↗
fn aws_auth_error_to_codex_error(error: AwsAuthError) -> CodexErr

作用:把 AWS 凭证加载阶段的错误翻译成项目统一使用的 CodexErr。这样上层看到的是统一格式的失败信息。

数据流:进去的是 AwsAuthError;它把错误文字拼进“failed to resolve Amazon Bedrock auth”这类说明里;出来的是 CodexErr::Fatal,表示这是启动认证时无法继续的严重错误。

调用关系:resolve_auth_method 在 AwsAuthContext::load 失败时会用它做错误转换。它不修复错误,只负责把 AWS 库的错误换成项目自己的错误类型。

调用图:外部调用 2 个(format!, Fatal)。

aws_auth_error_to_auth_error103–109 ↗
fn aws_auth_error_to_auth_error(error: AwsAuthError) -> AuthError

作用:把请求签名阶段的 AWS 错误翻译成认证错误,并区分“可以稍后再试”和“请求本身构造失败”。

数据流:进去的是 AwsAuthError;它先问错误 is_retryable,也就是是否值得重试;如果可重试,就输出 AuthError::Transient,表示临时问题;否则输出 AuthError::Build,表示构造认证信息时失败。

调用关系:BedrockMantleSigV4AuthProvider::apply_auth 在调用 AWS 签名失败时会用它。它让上层重试逻辑能更聪明:临时故障可以再试,配置或构造问题就不要盲目重试。

调用图:调用 1 个内部函数(is_retryable);外部调用 3 个(to_string, Build, Transient)。

remove_headers_not_preserved_by_bedrock_mantle111–124 ↗
fn remove_headers_not_preserved_by_bedrock_mantle(headers: &mut HeaderMap)

作用:删除 Bedrock Mantle 不会保留的请求头,特别是名字里带下划线的头。这样可以避免签名时算进去的头,在 AWS 验证时却消失,从而导致认证失败。

数据流:进去的是一组可修改的 HTTP 请求头;它找出所有名字包含下划线的头,比如 session_id、thread_id;然后把这些头删掉;出来时原来的 HeaderMap 被就地修改,只保留 Mantle 会正常传递的头。

调用关系:BedrockMantleSigV4AuthProvider::apply_auth 在签名前会调用它。对应测试 bedrock_mantle_sigv4_strips_headers_not_preserved_by_mantle 会专门验证它确实删掉下划线头,同时保留像 x-client-request-id 这样的正常头。

调用图:被 2 处调用(apply_auth, bedrock_mantle_sigv4_strips_headers_not_preserved_by_mantle);外部调用 2 个(keys, remove)。

BedrockMantleSigV4AuthProvider::new133–135 ↗
fn new(context: AwsAuthContext) -> Self

作用:创建一个会给 Bedrock Mantle 请求做 AWS SigV4 签名的认证提供者。它把已经加载好的 AWS 认证上下文保存起来,等每次发请求时使用。

数据流:进去的是 AwsAuthContext,也就是 AWS 凭证和签名所需的信息;它把这个上下文放进 BedrockMantleSigV4AuthProvider 结构里;出来的是一个新的认证提供者实例。

调用关系:resolve_provider_auth 在判断需要走 AWS SDK 认证时会调用它。之后请求发送流程会通过 AuthProvider 接口调用这个实例来给每个请求签名。

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

BedrockMantleSigV4AuthProvider::add_auth_headers161–161 ↗
fn add_auth_headers(&self, _headers: &mut HeaderMap)

作用:这是为了满足通用 AuthProvider 接口而提供的方法,但在这里它什么也不做。原因是 SigV4 认证不能只靠提前塞几个头,必须看到完整请求后再签名。

数据流:进去的是一组可修改的请求头;函数不读取也不改动它;出来时请求头保持原样。

调用关系:通用认证接口可能会先尝试让认证器加请求头,但 Bedrock Mantle 的 SigV4 路线真正的工作在 apply_auth 里完成。这个空实现是在告诉系统:别在这个阶段加头,等完整请求准备好再签。

BedrockMantleSigV4AuthProvider::apply_auth163–165 ↗
fn apply_auth(&self, request: Request) -> codex_api::AuthProviderFuture<'_>

作用:给一整个 Bedrock Mantle 请求加上 AWS SigV4 认证。它会准备请求正文、清理不兼容的请求头、调用 AWS 签名器,然后把签好的网址、请求头和正文放回请求里。

数据流:进去的是一个待发送的 Request;它先删掉 Mantle 不保留的下划线请求头,再把请求正文整理成可签名的字节,接着把方法、网址、请求头和正文交给 AwsAuthContext.sign;出来的是签名后的 Request,并且压缩会被关掉,正文会变成已经准备好的原始字节。

调用关系:这是真正发生在每次请求发送前的认证步骤。AuthProvider 接口会把调用包装成异步任务;它内部依次依赖 remove_headers_not_preserved_by_bedrock_mantle、prepare_body_for_send 和 sign,签名失败时交给 aws_auth_error_to_auth_error 翻译错误。

调用图:调用 3 个内部函数(sign, prepare_body_for_send, remove_headers_not_preserved_by_bedrock_mantle);外部调用 1 个(pin)。

tests::missing_env_var176–178 ↗
fn missing_env_var(_: &'static str) -> std::result::Result<String, std::env::VarError>

作用:测试用的小替身,用来假装某个环境变量不存在。这样测试可以稳定复现“没配置区域”的情况。

数据流:进去的是环境变量名字,但它不关心具体名字;它总是返回 VarError::NotPresent;出来的是一个“变量不存在”的结果。

调用关系:bedrock_bearer_auth_rejects_missing_configured_region 会把它传给 bearer_token_region,用来确认缺少所有区域来源时会报错。

tests::bedrock_bearer_auth_prefers_configured_region_and_uses_header181–210 ↗
fn bedrock_bearer_auth_prefers_configured_region_and_uses_header()

作用:验证 bearer token 认证会优先使用配置文件里的区域,并且会生成 Authorization 请求头。也就是说,配置里的 region 比环境变量更优先。

数据流:进去的是测试里手工造出的 token、AWS 配置和假的环境变量读取函数;它调用 bearer_token_region 得到区域,又创建 BearerAuthProvider 往空请求头里加认证;最后检查区域是不是 us-west-2,并检查 Authorization 是否以 Bearer bedrock-api-key- 开头。

调用关系:这个测试直接覆盖 bearer_token_region 的优先级规则,也顺带验证 BearerAuthProvider 和这个文件的 token 路线能配合工作。

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

tests::bedrock_bearer_auth_uses_aws_region_env213–227 ↗
fn bedrock_bearer_auth_uses_aws_region_env()

作用:验证如果配置里没有区域,就会使用 AWS_REGION 环境变量。这个测试保证常见的 AWS 环境配置方式能正常工作。

数据流:进去的是没有 region 的 AWS 配置,以及一个只在读取 AWS_REGION 时返回 eu-central-1 的假环境函数;它调用 bearer_token_region;出来的断言结果要求区域等于 eu-central-1。

调用关系:它专门检查 bearer_token_region 的第二优先级来源。这样 resolve_auth_method 在 token 路线下依赖它时,就不会因为用户只设置 AWS_REGION 而失败。

调用图:调用 1 个内部函数(bearer_token_region);外部调用 1 个(assert_eq!)。

tests::bedrock_bearer_auth_uses_aws_default_region_env230–244 ↗
fn bedrock_bearer_auth_uses_aws_default_region_env()

作用:验证如果配置和 AWS_REGION 都没有区域,还会退一步使用 AWS_DEFAULT_REGION。这个环境变量也是 AWS 工具链里常见的备用区域设置。

数据流:进去的是没有 region 的 AWS 配置,以及一个只在读取 AWS_DEFAULT_REGION 时返回 ap-northeast-1 的假环境函数;它调用 bearer_token_region;出来的断言结果要求区域等于 ap-northeast-1。

调用关系:它检查 bearer_token_region 的第三优先级来源,保证用户使用 AWS_DEFAULT_REGION 时 bearer token 认证仍然能找到区域。

调用图:调用 1 个内部函数(bearer_token_region);外部调用 1 个(assert_eq!)。

tests::bedrock_bearer_auth_rejects_missing_configured_region247–262 ↗
fn bedrock_bearer_auth_rejects_missing_configured_region()

作用:验证 bearer token 认证在完全找不到区域时会明确失败。没有这个检查,后面请求可能会以更难懂的方式失败。

数据流:进去的是没有 region 的 AWS 配置,以及总是返回“环境变量不存在”的 missing_env_var;它调用 bearer_token_region 并期待错误;出来的断言会检查错误文字是否清楚告诉用户要配置 model_providers.amazon-bedrock.aws.region、AWS_REGION 或 AWS_DEFAULT_REGION。

调用关系:它用 tests::missing_env_var 模拟空环境,直接测试 bearer_token_region 的失败路径。这个失败路径会被 resolve_auth_method 在 token 认证场景里继承使用。

调用图:调用 1 个内部函数(bearer_token_region);外部调用 1 个(assert_eq!)。

tests::bedrock_mantle_sigv4_strips_headers_not_preserved_by_mantle265–295 ↗
fn bedrock_mantle_sigv4_strips_headers_not_preserved_by_mantle()

作用:验证签名前清理请求头的规则:带下划线的头要删掉,不带下划线的正常头要留下。这个测试保护的是一个很实际的兼容性坑。

数据流:进去的是测试手工创建的一组请求头,其中 session_id、thread_id、future_identity_header 都带下划线,x-client-request-id 不带;它调用 remove_headers_not_preserved_by_bedrock_mantle;出来后断言前三个已经不存在,而 x-client-request-id 的值仍然保留。

调用关系:它直接覆盖 remove_headers_not_preserved_by_bedrock_mantle 的行为。因为 BedrockMantleSigV4AuthProvider::apply_auth 每次签名前都会调用这个清理函数,所以这个测试能防止将来改代码时重新引入签名失败的问题。

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

model-provider/src/amazon_bedrock/mantle.rs源码 ↗
domain_logic配置读取、认证解析、请求发起前

Amazon Bedrock Mantle 是跑在 AWS(亚马逊云)上的模型接口,这个文件就像一张“出行指南”:先从配置里拿地区和账号资料,再判断这个地区能不能用,最后拼出真正访问的网络地址。它还会为 AWS 认证准备一份 AwsAuthConfig,也就是告诉认证系统:用哪个 profile(本地保存的一套 AWS 身份)、哪个 region(AWS 地区)、哪个 service(要访问的 AWS 服务名)。这里有一个重要保护:只有白名单里的地区才会生成地址,不支持的地区会直接报致命错误,防止请求发出去后才莫名其妙失败。运行时如果身份来自托管令牌、环境变量令牌或 AWS SDK,它都会先解析出实际地区,再统一交给 base_url 拼地址。

函数细节9
aws_auth_config26–32 ↗
fn aws_auth_config(aws: &ModelProviderAwsAuthInfo) -> AwsAuthConfig

作用:把用户配置里的 AWS 身份信息整理成认证系统能直接使用的格式。它特别会把服务名固定成 bedrock-mantle,避免后面签名认证时把服务认错。

数据流:输入是一份 ModelProviderAwsAuthInfo,里面可能有 profile 和 region。它先复制 profile,再调用 region_from_config 清理并取出地区,最后输出 AwsAuthConfig;它不修改原配置,只生成一份新的认证说明。

调用关系:它是认证流程中的准备步骤,会被 resolve_auth_method 使用。自己不做复杂判断,只把地区清理工作交给 region_from_config,然后把结果塞进 AWS 认证配置里。

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

region_from_config34–40 ↗
fn region_from_config(aws: &ModelProviderAwsAuthInfo) -> Option<String>

作用:从配置里安全地取出 AWS 地区。它会顺手去掉前后空格,并把空字符串当作没填,避免“看起来填了其实没用”的配置坑。

数据流:输入是 ModelProviderAwsAuthInfo。它读取其中的 region:如果是 None、全是空格或空字符串,就输出 None;如果有真实内容,就输出去掉前后空格后的字符串。

调用关系:它是很多地方取地区时的小工具。aws_auth_config 用它准备 AWS 认证配置,bearer_token_region 也会用它决定令牌相关的地区。

调用图:被 2 处调用(bearer_token_region, aws_auth_config)。

base_url42–50 ↗
fn base_url(region: &str) -> Result<String>

作用:根据 AWS 地区拼出 Bedrock Mantle 的 OpenAI 兼容接口地址。它同时充当门卫:地区不在支持列表里,就立刻拒绝。

数据流:输入是一个地区字符串,比如 ap-northeast-1。它先检查这个地区是否在 Bedrock Mantle 支持的地区清单里;如果支持,就输出类似 https://bedrock-mantle.ap-northeast-1.api.aws/openai/v1 的地址;如果不支持,就输出一个致命错误。

调用关系:runtime_base_url 在拿到运行时地区后会调用它生成最终地址。测试函数 base_url_rejects_unsupported_region 会故意传入不支持的地区确认它会报错,其他集成测试也会依赖它确保配置地区会反映到接口地址里。

调用图:被 3 处调用(runtime_base_url, base_url_rejects_unsupported_region, api_provider_for_bedrock_bearer_token_uses_configured_region_endpoint);外部调用 2 个(format!, Fatal)。

runtime_base_url52–58 ↗
async fn runtime_base_url(
    managed_auth: Option<&BedrockApiKeyAuth>,
    aws: &ModelProviderAwsAuthInfo,
) -> Result<String>

作用:在程序真正运行时算出 Bedrock Mantle 的访问地址。它不是只看静态配置,而是会结合当前使用的认证方式来决定真实地区。

数据流:输入包括可能存在的托管 API key 认证信息,以及 AWS 配置。它先调用 resolve_region 找到实际应该使用的地区,再把这个地区交给 base_url;最终输出可访问的基础网址,或者输出错误。

调用关系:它处在“准备发请求”之前的关键位置,会被 api_provider 等上层创建模型提供方的流程调用。它把“找地区”的活交给 resolve_region,把“拼地址和检查地区”的活交给 base_url。

调用图:调用 2 个内部函数(base_url, resolve_region);被 2 处调用(api_provider, runtime_base_url)。

resolve_region60–69 ↗
async fn resolve_region(
    managed_auth: Option<&BedrockApiKeyAuth>,
    aws: &ModelProviderAwsAuthInfo,
) -> Result<String>

作用:根据当前认证方式找出真正要使用的 AWS 地区。因为地区可能来自托管令牌、环境变量令牌,也可能来自 AWS SDK 上下文,所以这里统一做归口判断。

数据流:输入是可选的 BedrockApiKeyAuth 和 AWS 配置。它先调用 resolve_auth_method 判断现在到底用哪种认证;如果是 bearer token(承载令牌,一段可直接证明身份的字符串),就取令牌里的地区;如果是 AWS SDK 认证,就从 SDK 的上下文里取地区;最后输出地区字符串。

调用关系:它只被 runtime_base_url 使用,是运行时生成地址前的地区解析步骤。它依赖 resolve_auth_method 先把认证方式分清楚,然后自己只负责从不同认证结果里拿出 region。

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

tests::base_url_uses_region_endpoint78–83 ↗
fn base_url_uses_region_endpoint()

作用:这个测试确认支持的地区会被正确放进 Mantle 地址里。它防止以后改代码时把地址格式拼坏。

数据流:测试输入是 ap-northeast-1 这个支持地区。它调用 base_url,拿到结果后和预期网址做比较;如果不一致,测试失败,不会改动运行时数据。

调用关系:它是 base_url 的正向测试,证明正常地区能生成正确 endpoint(服务入口地址)。它只在测试时运行,用 assert_eq! 做结果校验。

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

tests::base_url_rejects_unsupported_region86–93 ↗
fn base_url_rejects_unsupported_region()

作用:这个测试确认不支持的地区会被拒绝,而不是生成一个看似可用但实际会失败的网址。

数据流:测试输入是 us-west-1 这个不在支持列表里的地区。它调用 base_url,期待得到错误,再检查错误文字是否明确说明 Bedrock Mantle 不支持该地区。

调用关系:它专门覆盖 base_url 的错误分支。测试时它用 assert_eq! 检查错误信息,确保用户以后看到的提示也不会变得含糊。

调用图:调用 1 个内部函数(base_url);外部调用 1 个(assert_eq!)。

tests::aws_auth_config_uses_profile_and_mantle_service96–108 ↗
fn aws_auth_config_uses_profile_and_mantle_service()

作用:这个测试确认 AWS profile 会被保留下来,而且服务名一定是 bedrock-mantle。这样可以防止认证签名用错服务。

数据流:测试输入是一份带 profile、没有 region 的 AWS 配置。它调用 aws_auth_config,检查输出里 profile 原样存在、region 仍为空、service 被设置为 bedrock-mantle。

调用关系:它验证 aws_auth_config 的基本行为。虽然函数内部会用 region_from_config,但这个测试主要关心 profile 和固定服务名是否正确。

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

tests::aws_auth_config_uses_configured_region111–123 ↗
fn aws_auth_config_uses_configured_region()

作用:这个测试确认配置里的地区会被采用,并且前后多余空格会被清掉。它防止用户配置里多打空格时导致认证地区出错。

数据流:测试输入是一份 region 为“ us-west-2 ”的 AWS 配置。它调用 aws_auth_config,期望输出的 region 变成干净的“us-west-2”,同时 service 仍是 bedrock-mantle。

调用关系:它验证 aws_auth_config 和 region_from_config 的配合效果。测试时通过 assert_eq! 比较完整输出,确保地区清理和服务名设置都符合预期。

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

model-provider/src/amazon_bedrock/mod.rs源码 ↗
domain_logiccross-cutting

Amazon Bedrock 是亚马逊云上的模型服务,但项目内部需要一个统一入口来调用不同模型平台。这个文件就是 Bedrock 的适配器,像一个转换插头:外面看起来是项目通用的 ModelProvider,里面会按 Bedrock 的规则找认证信息、拼运行时网址、给出模型清单。它特别注意两种凭证来源:如果 Codex 自己保存了 Bedrock API Key,就优先用它;否则就走 AWS 自己管理的凭证,比如本机 AWS 配置。它还明确告诉上层:Bedrock 这里支持带命名空间的工具,但不支持托管的图片生成和网页搜索。模型列表不去实时拉网络,而是用静态目录,必要时合并配置里的模型信息。

函数细节19
AmazonBedrockModelProvider::new42–58 ↗
fn new(
        provider_info: ModelProviderInfo,
        auth_manager: Option<Arc<AuthManager>>,
    ) -> Self

作用:创建一个 Bedrock 模型提供商对象。调用方给它一份提供商配置和可选的登录管理器,它会准备好后面连接 Bedrock 需要的基本信息。

数据流:输入是 ModelProviderInfo 和可选 AuthManager。函数先从配置里取 AWS 的 profile 和 region;如果没有,就放一份空的 AWS 认证信息作为默认值。输出是一个 AmazonBedrockModelProvider,里面保存了提供商信息、AWS 信息和登录管理器。

调用关系:这是 Bedrock 提供商的起点。正常运行时由 create_model_provider 调用来造对象;测试里也反复用它来搭出不同登录场景,检查后续行为是不是正确。

调用图:被 5 处调用(approval_review_preferred_model_uses_bedrock_gpt_5_4, capabilities_disable_unsupported_hosted_tools, managed_auth_takes_precedence_over_aws_auth, openai_auth_is_not_exposed_to_bedrock, create_model_provider)。

AmazonBedrockModelProvider::managed_auth60–72 ↗
fn managed_auth(&self) -> Option<BedrockApiKeyAuth>

作用:从项目自己的登录缓存里找 Bedrock 专用 API Key。它会刻意忽略 OpenAI、ChatGPT 等其他类型的登录信息,避免把错的钥匙拿去开 Bedrock 的门。

数据流:输入是 provider 自己保存的 auth_manager。它先看有没有登录管理器,再看缓存里有没有认证信息;只有认证信息正好是 BedrockApiKey 时才取出。输出是可选的 BedrockApiKeyAuth;如果没有或类型不对,就输出 None。

调用关系:这是本文件判断“有没有 Codex 托管的 Bedrock 凭证”的核心小工具。account_state、api_auth、api_provider、auth、auth_manager 和 runtime_base_url 都会先问它一声,再决定走 Codex API Key 还是 AWS 凭证。

调用图:被 6 处调用(account_state, api_auth, api_provider, auth, auth_manager, runtime_base_url)。

AmazonBedrockModelProvider::info100–102 ↗
fn info(&self) -> &ModelProviderInfo

作用:把这个 Bedrock 提供商的基本资料交给上层,比如名字、配置、默认信息等。它不改东西,只是把保存好的信息拿出来看。

数据流:输入是 provider 本身。函数直接读取其中的 info 字段,并返回它的引用。结果是上层可以查看这份 ModelProviderInfo,但这里不会复制或修改它。

调用关系:这是 ModelProvider 接口要求提供的基础查询方法。上层需要了解当前提供商配置时会调用它,它不再把工作交给其他函数。

AmazonBedrockModelProvider::capabilities104–110 ↗
fn capabilities(&self) -> ProviderCapabilities

作用:告诉系统 Bedrock 这个提供商支持什么、不支持什么。这样上层就不会把图片生成或网页搜索这类 Bedrock 当前不支持的托管能力硬塞给它。

数据流:输入是 provider 本身,但这里不需要读取复杂状态。函数直接构造一个 ProviderCapabilities:namespace_tools 为 true,image_generation 和 web_search 为 false。输出就是这份能力说明。

调用关系:这是上层安排任务前会看的“能力标签”。测试 capabilities_disable_unsupported_hosted_tools 会检查这些开关,确保系统不会误以为 Bedrock 能做不支持的事。

AmazonBedrockModelProvider::approval_review_preferred_model112–114 ↗
fn approval_review_preferred_model(&self) -> &'static str

作用:给“审批审查”这类内部任务指定 Bedrock 上优先使用的模型。这样系统不用临时猜该挑哪个模型。

数据流:输入是 provider 本身。函数不查外部信息,直接返回一个固定的 Bedrock GPT 5.4 模型 ID。输出是模型名字符串。

调用关系:这是 ModelProvider 接口的一部分,上层在需要做审批审查时会问它推荐模型。测试 approval_review_preferred_model_uses_bedrock_gpt_5_4 专门确认它返回的是 Bedrock GPT 5.4。

AmazonBedrockModelProvider::memory_extraction_preferred_model116–118 ↗
fn memory_extraction_preferred_model(&self) -> &'static str

作用:给“记忆提取”任务指定 Bedrock 上优先使用的模型。记忆提取就是从对话或内容里挑出值得保存的信息。

数据流:输入是 provider 本身。函数直接返回固定的 Bedrock GPT 5.4 模型 ID。输出是给上层调度使用的模型名。

调用关系:上层做记忆相关流程时会通过 ModelProvider 接口询问推荐模型。这个函数不调用其他本地函数,只提供一个稳定选择。

AmazonBedrockModelProvider::memory_consolidation_preferred_model120–122 ↗
fn memory_consolidation_preferred_model(&self) -> &'static str

作用:给“记忆整理合并”任务指定 Bedrock 上优先使用的模型。它让系统在整理多条记忆时有一个默认可靠的模型选择。

数据流:输入是 provider 本身。函数直接拿固定常量作为结果,返回 Bedrock GPT 5.4 模型 ID。它不修改 provider,也不访问网络。

调用关系:上层在需要合并、归纳已有记忆时会用到这个接口。它和其他推荐模型函数一样,是给调度层看的默认答案。

AmazonBedrockModelProvider::auth_manager124–127 ↗
fn auth_manager(&self) -> Option<Arc<AuthManager>>

作用:只在确认登录管理器里确实保存了 Bedrock 专用凭证时,才把这个登录管理器暴露给上层。这样可以防止 OpenAI 的钥匙被误认为 Bedrock 的钥匙。

数据流:输入是 provider 自己保存的 auth_manager。函数先调用 managed_auth 检查里面有没有 BedrockApiKey;如果有,就返回同一个 AuthManager 的共享引用;如果没有,就返回 None。它不会改变登录状态。

调用关系:它依赖 managed_auth 做安全筛选。测试 managed_auth_takes_precedence_over_aws_auth 会确认 Bedrock 凭证存在时能暴露;openai_auth_is_not_exposed_to_bedrock 会确认 OpenAI 凭证不会被暴露给 Bedrock。

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

AmazonBedrockModelProvider::auth129–131 ↗
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>>

作用:把当前可用的 Bedrock 登录信息交给上层。这里返回的是异步任务,因为统一接口要求认证查询可以异步执行。

数据流:输入是 provider 本身。函数把内部的认证获取逻辑包装成一个可等待的 Future;真正执行时会调用 managed_auth,如果找到 Bedrock API Key,就包装成 CodexAuth::BedrockApiKey 返回,否则返回 None。

调用关系:这是 ModelProvider 接口对外的认证入口。它把同步的小检查 managed_auth 放进异步外壳里,方便上层用统一方式处理不同提供商的认证。

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

AmazonBedrockModelProvider::account_state133–143 ↗
fn account_state(&self) -> ProviderAccountResult

作用:告诉上层当前 Bedrock 账号状态,以及凭证来自哪里。重点是区分“Codex 自己保存的 Bedrock Key”和“AWS 环境里的凭证”。

数据流:输入是 provider 本身。函数调用 managed_auth;如果有 Bedrock Key,就把 credential_source 标成 CodexManaged,否则标成 AwsManaged。输出是 ProviderAccountState,并明确 requires_openai_auth 为 false,意思是这里不需要 OpenAI 登录。

调用关系:账号展示、诊断或登录提示会用到这个结果。它依赖 managed_auth 判断来源,测试里会覆盖 Bedrock Key 和 OpenAI Key 两种情况,确保来源标记不会混。

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

AmazonBedrockModelProvider::api_provider145–147 ↗
fn api_provider(&self) -> ModelProviderFuture<'_, Result<Provider>>

作用:生成真正发请求时要用的 API 提供商配置。它会把 Bedrock 的运行时网址补进去,让后续请求知道该连到哪个区域的 Mantle 地址。

数据流:输入是 provider 本身。函数先调用 managed_auth 看有没有 Codex 托管的 Bedrock Key,再复制一份 provider info,然后通过 runtime_base_url 算出实际 base_url 填进去,最后把这份信息转换成 codex_api::Provider。输出是可用于发送 API 请求的 Provider,或错误。

调用关系:这是上层准备调用模型前的重要步骤。它把认证来源、AWS 区域信息和通用 Provider 配置串起来;内部会用到 mantle 的 runtime_base_url,并用 clone 保护原始配置不被改坏。

调用图:调用 2 个内部函数(managed_auth, runtime_base_url);外部调用 2 个(pin, clone)。

AmazonBedrockModelProvider::runtime_base_url149–151 ↗
fn runtime_base_url(&self) -> ModelProviderFuture<'_, Result<Option<String>>>

作用:算出运行时真正要访问的 Bedrock Mantle 网址。因为 Bedrock 有区域差异,同一个服务在不同 AWS region 会有不同地址。

数据流:输入是 provider 里的 AWS 信息和可能存在的 Bedrock API Key。函数先调用 managed_auth,再把认证和 AWS 配置交给 mantle::runtime_base_url 计算。输出是 Some(url);如果区域不支持或配置有问题,就返回错误。

调用关系:api_provider 会用它把 Provider 的 base_url 填好;上层也可以单独问它当前会连到哪里。测试 managed_auth_takes_precedence_over_aws_auth 会确认有托管 Bedrock Key 时优先使用 Key 里的 region。

调用图:调用 2 个内部函数(managed_auth, runtime_base_url);外部调用 1 个(pin)。

AmazonBedrockModelProvider::api_auth153–155 ↗
fn api_auth(&self) -> ModelProviderFuture<'_, Result<SharedAuthProvider>>

作用:准备真正发 API 请求时用的认证头或签名方式。简单说,就是把“我是谁、凭什么访问 Bedrock”转换成 HTTP 请求能带上的认证信息。

数据流:输入是 provider 里的托管 Bedrock Key 和 AWS 配置。函数先调用 managed_auth;如果有 Key,就交给 resolve_provider_auth 生成 Bearer token 这类认证提供者;如果没有,就让它按 AWS 凭证方式解析。输出是 SharedAuthProvider,或解析失败的错误。

调用关系:模型请求发出前会调用它来拿认证材料。它把认证细节交给 auth 模块里的 resolve_provider_auth,自己负责决定传入托管 Key 还是 AWS 配置。

调用图:调用 1 个内部函数(managed_auth);外部调用 2 个(pin, resolve_provider_auth)。

AmazonBedrockModelProvider::models_manager157–166 ↗
fn models_manager(
        &self,
        _codex_home: PathBuf,
        config_model_catalog: Option<ModelsResponse>,
    ) -> SharedModelsManager

作用:给 Bedrock 准备一个模型列表管理器。这里不用动态联网拉模型,而是使用内置的静态模型目录,保证可预测、启动快。

数据流:输入有 codex_home 路径和可选的配置模型目录。函数忽略 codex_home;如果外部配置给了模型目录,就用 with_default_only_service_tier 整理它;如果没给,就用 static_model_catalog。然后创建 StaticModelsManager,并用 Arc 包成可共享对象返回。

调用关系:上层需要列出或选择可用模型时会用这个管理器。它会调用 StaticModelsManager::new,并间接使用本模块导出的静态目录工具,和认证管理器无关。

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

tests::api_provider_for_bedrock_bearer_token_uses_configured_region_endpoint177–190 ↗
fn api_provider_for_bedrock_bearer_token_uses_configured_region_endpoint()

作用:这个测试确认:按指定 AWS 区域生成的 Bedrock Mantle 地址是对的。它防止把请求发到错误区域或错误域名。

数据流:输入是测试里写死的 region:eu-central-1。测试创建 Bedrock provider 配置,调用 mantle::base_url 生成 base_url,再转成 API Provider。最后断言得到的网址等于 https://bedrock-mantle.eu-central-1.api.aws/openai/v1。

调用关系:它验证底层网址拼接逻辑能被 Provider 配置正确使用。它调用 create_amazon_bedrock_provider 和 base_url,不经过 AmazonBedrockModelProvider::new。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, base_url);外部调用 1 个(assert_eq!)。

tests::managed_auth_takes_precedence_over_aws_auth193–243 ↗
async fn managed_auth_takes_precedence_over_aws_auth()

作用:这个测试确认:如果 Codex 里已经保存了 Bedrock API Key,就优先用它,而不是去读 AWS profile。这样用户明确登录的 Bedrock Key 不会被本机 AWS 配置抢走。

数据流:输入是一份假的 Bedrock API Key,region 是 us-east-1,同时配置里故意放了另一个 AWS profile 和 us-west-2。测试创建 AuthManager 和 provider 后,检查 auth_manager 能暴露、auth 返回 Bedrock Key、账号来源是 CodexManaged、运行网址使用 us-east-1、认证头是 Bearer managed-bedrock-api-key。

调用关系:它覆盖 new、auth_manager、auth、account_state、runtime_base_url 和 api_auth 这条主路径。它说明 managed_auth 是认证优先级的关键判断点。

调用图:调用 3 个内部函数(from_auth_for_testing, create_amazon_bedrock_provider, new);外部调用 3 个(assert!, assert_eq!, BedrockApiKey)。

tests::openai_auth_is_not_exposed_to_bedrock246–265 ↗
async fn openai_auth_is_not_exposed_to_bedrock()

作用:这个测试确认 OpenAI API Key 不会被 Bedrock 使用。它防止不同平台的钥匙串门,避免认证失败或安全混乱。

数据流:输入是一份假的 OpenAI API Key。测试把它放进 AuthManager,再创建 Bedrock provider。之后检查 auth_manager 返回 None、auth 返回 None,并且账号状态显示 Bedrock 会走 AwsManaged,而不是 CodexManaged。

调用关系:它主要验证 managed_auth 的过滤规则。它调用 create_amazon_bedrock_provider 和 new 搭环境,再通过 provider 的公开接口确认 OpenAI 凭证被正确忽略。

调用图:调用 4 个内部函数(from_auth_for_testing, from_api_key, create_amazon_bedrock_provider, new);外部调用 2 个(assert!, assert_eq!)。

tests::capabilities_disable_unsupported_hosted_tools268–282 ↗
fn capabilities_disable_unsupported_hosted_tools()

作用:这个测试确认 Bedrock 的能力开关写得符合实际:支持 namespace tools,但不支持托管图片生成和网页搜索。

数据流:输入是默认的 Bedrock provider 配置。测试创建 provider,调用 capabilities,然后把结果和预期的 ProviderCapabilities 作比较。输出不是业务数据,而是测试通过或失败。

调用关系:它保护 capabilities 这个接口不被误改。上层是否展示或调用某些功能,会依赖这些能力开关。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, new);外部调用 1 个(assert_eq!)。

tests::approval_review_preferred_model_uses_bedrock_gpt_5_4285–295 ↗
fn approval_review_preferred_model_uses_bedrock_gpt_5_4()

作用:这个测试确认审批审查任务默认使用 Bedrock GPT 5.4 模型。它防止默认模型被无意中改成别的模型。

数据流:输入是默认 Bedrock provider 配置。测试创建 provider,调用 approval_review_preferred_model,然后断言返回值等于 AMAZON_BEDROCK_GPT_5_4_MODEL_ID。结果是测试通过或失败。

调用关系:它保护 approval_review_preferred_model 的固定选择。上层做审批审查时会依赖这个默认模型,因此这个测试能及时发现错误改动。

调用图:调用 2 个内部函数(create_amazon_bedrock_provider, new);外部调用 1 个(assert_eq!)。

客户端认证抽象

提供共享认证接口和证明边界,供下游 HTTP 风格客户端在向请求附加凭据时使用。

core/src/attestation.rs源码 ↗
data_modelrequest handling

这个文件解决的是一个边界问题:核心代码在准备向上游发送请求时,可能需要带上一段“证明信息”,用来说明这个请求来自可信的运行环境或可信的宿主方。这个证明放在 HTTP 请求头里,名字固定叫 x-oai-attestation。但核心代码并不知道每个平台该不该生成、怎么生成,所以它只定义一套“插槽”。AttestationContext 像一张小纸条,告诉外部实现:当前是在为哪个线程的请求准备证明。AttestationProvider 是一个约定接口,外部系统实现它以后,就可以在请求快发出前临时决定是否返回一个请求头值。返回值是异步的,因为生成证明可能要等系统服务、硬件或网络结果。没有这个文件,核心代码和具体宿主环境就会绑死,既不灵活,也不好移植。

codex-api/src/auth.rs源码 ↗
domain_logiccross-cutting / request handling

这个文件定义了一套认证接口,意思是:不管以后用的是简单的请求头认证,还是更复杂的“给整个请求签名”,外面的 API 客户端都按同一种方式调用。核心是 AuthProvider 这个 trait(可以理解成一份接口约定):实现者要能往请求头里塞认证信息,也可以在请求真正发出去前检查完整的地址、请求头和内容,再决定怎么改请求。AuthError 用来区分两类问题:一种是请求本身组装认证失败,另一种是临时性认证失败,并且会转换成底层传输错误,方便网络发送层统一处理。文件还提供 auth_header_telemetry,用来悄悄检查是否带了认证头,给遥测统计用,但不会暴露真正的密钥内容。

函数细节4
TransportError::from18–23 ↗
fn from(error: AuthError) -> Self

作用:这个函数把认证阶段出的错,翻译成发送请求时通用的传输错误。这样上层不用分别理解“认证错误”和“网络发送错误”两套错误说法。

数据流:进去的是一个 AuthError:可能是认证信息构造失败,也可能是临时认证问题。函数根据错误种类改成 TransportError:构造失败变成 Build,临时问题变成 Network。出来的是发送层能识别的统一错误值,不会继续发送这个请求。

调用关系:它夹在认证逻辑和请求传输逻辑之间。认证代码发现问题后,可以交给这个转换函数,把错误包装成 codex_client 使用的 TransportError;内部会分别走 Build 或 Network 这两个外部错误构造入口。

调用图:外部调用 2 个(Build, Network)。

AuthProvider::to_auth_headers38–42 ↗
fn to_auth_headers(&self) -> HeaderMap

作用:这个函数把认证提供者能直接给出的认证请求头整理成一份新的 HeaderMap。别人如果只想拿认证头,而不是改一个完整请求,就会用它。

数据流:进去的是一个认证提供者自身。函数先新建一份空的请求头集合,然后调用 add_auth_headers 往里面填认证信息。出来的是这份已经填好的 HeaderMap;原来的请求或其他数据不会被改动。

调用关系:它是 AuthProvider 接口里的默认小工具。实现者只要写好 add_auth_headers,这个函数就能自动工作;它依赖外部的 HeaderMap::new 创建空表,再把填头的活交给具体认证实现。

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

AuthProvider::apply_auth55–61 ↗
fn apply_auth(&self, request: Request) -> AuthProviderFuture<'_>

作用:这个函数是在请求真正发出去前,给完整请求套上认证信息。默认做法很简单:只往请求头里加认证内容;更复杂的认证方式可以覆盖它,比如需要看请求正文后再签名。

数据流:进去的是一个完整的 Request,请求所有权会交给这个函数。默认实现会把请求拿过来,往它的 headers 里加入认证头,然后返回修改后的 Request。如果出错,则返回 AuthError,表示这个请求不该再发送。

调用关系:它是发送请求前的关键一站。API 客户端或传输层应当先调用它,并且必须使用它返回的请求作为最终版本。默认实现内部把异步结果用 pin 包起来,方便调用方用统一的异步方式等待认证完成。

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

auth_header_telemetry76–86 ↗
fn auth_header_telemetry(auth: &dyn AuthProvider) -> AuthHeaderTelemetry

作用:这个函数用来做很克制的认证遥测:只记录有没有加认证头,以及头的名字是不是 authorization,不记录真正的密钥内容。它帮助系统知道认证有没有挂上,同时避免泄露敏感信息。

数据流:进去的是一个认证提供者。函数新建一份空请求头,让认证提供者往里面加认证信息,然后检查里面是否有 Authorization 头。出来的是 AuthHeaderTelemetry:attached 表示是否带了认证头,name 最多只写入 "authorization" 这个固定名字。

调用关系:它通常服务于日志或遥测路径,而不是实际发请求。它不会自己生成认证信息,而是调用认证提供者的 add_auth_headers;它也不会碰真实请求,只用一份临时 HeaderMap 做检查。

调用图:外部调用 2 个(new, add_auth_headers)。

远程与 MCP 认证流程

为远程控制传输和 MCP 服务器适配认证状态,包括 OAuth 能力检测和状态合成。

app-server-transport/src/transport/remote_control/auth.rs源码 ↗
domain_logicremote control enrollment and reconnect/auth recovery

远程控制就像把一把遥控钥匙交给服务器,所以这里最重要的是确认“这把钥匙是谁的、能不能用”。这个文件先从 AuthManager(认证管理器,也就是保存和刷新登录状态的地方)里取当前登录信息;如果信息还没加载好,会尝试重新加载一次。它明确拒绝 API key 登录,因为远程控制需要绑定到具体 ChatGPT 账号,还必须拿到 account_id(账号标识)。如果登录过期或被服务器拒绝,recover_remote_control_auth 会按 UnauthorizedRecovery(未授权恢复流程)一步步尝试修复,比如刷新令牌。这里还有一个小心眼的细节:恢复登录本身可能会触发“认证信息已变化”的通知,mark_recovery_auth_change_seen 会只把这一次内部变化标记为已看过,避免外层重连循环被自己刚做的修复白白唤醒;但如果同时有别的登录变化,它不会吞掉,外层仍能处理。

函数细节3
load_remote_control_auth16–59 ↗
async fn load_remote_control_auth(
    auth_manager: &Arc<AuthManager>,
) -> io::Result<RemoteControlConnectionAuth>

作用:读取远程控制需要的登录身份,并确认它是 ChatGPT 账号登录。有人要配对、登记、发送远程控制管理请求时,会用它来拿到可用的认证提供器和账号编号。

数据流:进去的是一个 AuthManager(保存当前登录状态的认证管理器)→ 它先异步读取当前认证信息;如果没有,就重新加载一次再试;如果发现是 API key 登录,就返回“权限不够”的错误;如果是 ChatGPT 登录但缺少账号编号,也会提示还在等账号编号 → 成功时出来一个 RemoteControlConnectionAuth,里面有后续请求要用的 auth_provider(认证提供器,负责给请求加上登录凭证)和 account_id;失败时出来的是 io::Error,说明为什么不能继续。

调用关系:它是远程控制很多入口动作的第一道门卫。pairing_status、persist_preference、start_pairing、send_client_management_request、enable、resolve_persisted_preference、enroll_pairing_server、refresh_pairing_enrollment 等流程在真正做远程控制相关动作前,会先找它确认身份。它内部会在需要报错时创建 io 错误,并调用 auth_provider_from_auth 把登录信息包装成后续网络请求能使用的认证提供器。

调用图:被 11 处调用(pairing_status, persist_preference, start_pairing, send_client_management_request, enable, resolve_persisted_preference, enroll_pairing_server, refresh_pairing_enrollment, resolve_unknown_desired_state, prepare_remote_control_enrollment (+1 more));外部调用 2 个(new, auth_provider_from_auth)。

recover_remote_control_auth61–91 ↗
async fn recover_remote_control_auth(
    auth_recovery: &mut UnauthorizedRecovery,
    auth_change_rx: &mut watch::Receiver<u64>,
) -> bool

作用:当远程控制请求因为“未授权”失败时,这个函数尝试自动补救登录状态。它像是发现门禁卡失效后,先去刷新或重新确认一下卡,而不是马上放弃。

数据流:进去的是一个 UnauthorizedRecovery(未授权恢复流程,里面排好了可尝试的修复步骤)和一个 auth_change_rx(认证变化通知接收器,用来知道登录状态有没有变)→ 它先看还有没有下一步可试;有的话记录当前步骤名称和认证变化编号,然后执行下一步恢复;如果恢复成功且确实改了认证状态,就调用 mark_recovery_auth_change_seen 处理这次变化通知;同时写日志说明成功或失败 → 出来的是 true 或 false:true 表示这次恢复成功,外层可以继续试;false 表示没有可试步骤或恢复失败。

调用关系:它被 send_client_management_request、enroll_pairing_server、refresh_pairing_enrollment、enroll_and_persist_remote_control_server、prepare_remote_control_enrollment 等流程用在“认证失败后的补救阶段”。它自己会调用 UnauthorizedRecovery 的 has_next、mode_name、step_name、next 来推进恢复步骤;如果恢复造成认证状态变化,就把细节交给 mark_recovery_auth_change_seen,避免外层重连逻辑被不必要地打扰。

调用图:调用 5 个内部函数(mark_recovery_auth_change_seen, has_next, mode_name, next, step_name);被 5 处调用(send_client_management_request, enroll_pairing_server, refresh_pairing_enrollment, enroll_and_persist_remote_control_server, prepare_remote_control_enrollment);外部调用 3 个(borrow, info!, warn!)。

mark_recovery_auth_change_seen93–105 ↗
fn mark_recovery_auth_change_seen(
    auth_change_rx: &mut watch::Receiver<u64>,
    auth_change_revision_before_recovery: u64,
)

作用:把“刚才恢复登录自己造成的那一次认证变化”标记为已处理。这样系统不会因为自己刚刷新了登录信息,又误以为外部发生了新变化而重复重连。

数据流:进去的是认证变化通知接收器 auth_change_rx,以及恢复开始前看到的变化编号 → 它读取恢复后的编号;如果编号刚好只比之前多 1,说明大概率就是恢复流程自己触发的那一次变化,于是调用 borrow_and_update 把这次通知标记为已看过;如果编号变化不止一次,说明恢复期间可能还有别的登录变化插进来,它就不标记,留给外层流程处理 → 它没有返回值,只会更新接收器内部的“已看过”位置。

调用关系:它主要由 recover_remote_control_auth 在恢复成功后调用,是认证恢复和外层重连循环之间的缓冲垫。测试函数 mark_recovery_auth_change_seen_marks_only_recovery_revision_seen 和 mark_recovery_auth_change_seen_preserves_racing_auth_change 会验证两个关键场景:只吞掉恢复自己造成的那一次通知,以及不吞掉同时发生的外部认证变化。

调用图:被 3 处调用(recover_remote_control_auth, mark_recovery_auth_change_seen_marks_only_recovery_revision_seen, mark_recovery_auth_change_seen_preserves_racing_auth_change);外部调用 2 个(borrow, borrow_and_update)。

rmcp-client/src/auth_status.rs源码 ↗
domain_logic连接 MCP 服务器前的认证检查和 OAuth 能力探测

这个文件像一个“进门前的门禁检查员”。它先看用户有没有直接给 Bearer token(一种放在请求头里的通行证);再看默认 HTTP 头里有没有 Authorization;如果没有,就去本机保存的 OAuth 凭据里找是否已有可用登录;还没有的话,它会访问服务器约定好的 .well-known 地址,看看服务器是否公开了 OAuth 登录入口。OAuth 可以理解成“跳到网页授权后拿通行证”的登录方式。文件里还会清理服务器声明的 scopes(权限范围),比如去掉空白、去重。为了不让探测卡太久,网络发现请求有 5 秒超时。后半部分是测试:会临时启动一个小 HTTP 服务器,模拟真实服务器返回 OAuth 信息,并用环境变量保护器避免测试污染系统环境。

函数细节15
determine_streamable_http_auth_status32–68 ↗
async fn determine_streamable_http_auth_status(
    server_name: &str,
    url: &str,
    bearer_token_env_var: Option<&str>,
    http_headers: Option<HashMap<String, String>>,
    env_http_headers: O

作用:判断某个 HTTP MCP 服务器当前应该算是哪种登录状态。调用者用它来决定下一步是直接带 token 连接、走 OAuth、提示用户登录,还是认为服务器不支持认证发现。

数据流:输入服务器名、服务器地址、可能的 token 环境变量名、HTTP 头配置、OAuth 凭据保存方式和钥匙串后端 → 它先检查有没有 Bearer token 或 Authorization 请求头,再查本机保存的 OAuth token 状态,最后必要时访问服务器的 OAuth 发现地址 → 输出一个 McpAuthStatus,表示 BearerToken、OAuth、NotLoggedIn 或 Unsupported;它本身不改服务器,只读取配置、本地凭据并可能发一次网络探测。

调用关系:这是本文件的总判断入口。它把请求头整理工作交给 build_default_headers,把本地 OAuth 凭据检查交给 oauth_token_status,把服务器是否支持 OAuth 的网络探测交给 discover_streamable_http_oauth_with_headers;测试里的两个 Bearer token 场景会直接调用它确认优先级正确。

调用图:调用 3 个内部函数(discover_streamable_http_oauth_with_headers, oauth_token_status, build_default_headers);被 2 处调用(determine_auth_status_uses_bearer_token_when_authorization_header_present, determine_auth_status_uses_bearer_token_when_env_authorization_header_present);外部调用 1 个(debug!)。

supports_oauth_login71–77 ↗
async fn supports_oauth_login(url: &str) -> Result<bool>

作用:快速回答一个简单问题:这个服务器看起来支不支持 OAuth 登录。适合只关心“能不能网页登录授权”的地方使用。

数据流:输入一个服务器 URL → 它调用 discover_streamable_http_oauth 做标准发现,不额外传自定义请求头 → 如果发现结果存在就返回 true,否则返回 false;如果 URL 或网络解析出错,就把错误交给调用者。

调用关系:它是 discover_streamable_http_oauth 的简化包装。测试 supports_oauth_login_does_not_require_scopes_supported 用它确认:服务器即使没声明权限范围,只要有授权入口和换 token 入口,也算支持 OAuth。

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

discover_streamable_http_oauth79–86 ↗
async fn discover_streamable_http_oauth(
    url: &str,
    http_headers: Option<HashMap<String, String>>,
    env_http_headers: Option<HashMap<String, String>>,
) -> Result<Option<StreamableHttpOAuth

作用:去服务器上寻找 OAuth 发现信息,并允许调用者附带普通配置里的 HTTP 头。它比 supports_oauth_login 返回的信息更细,比如还能带回服务器支持的权限范围。

数据流:输入 URL、手写 HTTP 头、从环境变量读取的 HTTP 头配置 → 它先用 build_default_headers 合并并解析这些请求头 → 再把 URL 和整理好的头交给 discover_streamable_http_oauth_with_headers → 输出可能存在的 StreamableHttpOAuthDiscovery,里面主要是清理后的 scopes。

调用关系:它处在“公开接口”和“底层网络探测”之间。supports_oauth_login 用它做布尔判断;多个测试直接用它检查 scopes 是否被正确清理或忽略。

调用图:调用 2 个内部函数(discover_streamable_http_oauth_with_headers, build_default_headers);被 3 处调用(supports_oauth_login, discover_streamable_http_oauth_ignores_empty_scopes, discover_streamable_http_oauth_returns_normalized_scopes)。

discover_streamable_http_oauth_with_headers88–141 ↗
async fn discover_streamable_http_oauth_with_headers(
    url: &str,
    default_headers: &HeaderMap,
) -> Result<Option<StreamableHttpOAuthDiscovery>>

作用:真正发 HTTP 请求去探测服务器是否公开 OAuth 元数据。它负责按标准尝试几个可能的 .well-known 地址,并判断返回内容是否足够说明支持 OAuth。

数据流:输入服务器 URL 和已经整理好的默认请求头 → 它解析 URL,创建带 5 秒超时且不走代理的 HTTP 客户端,给请求加上默认头和 MCP 协议版本头,然后按 discovery_paths 生成的候选路径逐个 GET → 对返回 200 的 JSON,检查是否同时有 authorization_endpoint 和 token_endpoint;如果有就清理 scopes 并返回发现结果,否则继续试;全部失败则返回 None,严重解析或建客户端错误会返回错误。

调用关系:这是 OAuth 发现的核心执行者。determine_streamable_http_auth_status 在本地没有 token 时会用它兜底判断;discover_streamable_http_oauth 也把实际网络活儿交给它。它内部依赖 discovery_paths 决定访问哪些地址,依赖 normalize_scopes 整理权限范围,并用 apply_default_headers 把请求头装进 HTTP 客户端。

调用图:调用 3 个内部函数(discovery_paths, normalize_scopes, apply_default_headers);被 2 处调用(determine_streamable_http_auth_status, discover_streamable_http_oauth);外部调用 3 个(parse, builder, debug!)。

normalize_scopes153–173 ↗
fn normalize_scopes(scopes_supported: Option<Vec<String>>) -> Option<Vec<String>>

作用:把服务器返回的权限范围列表收拾干净。它会去掉前后空格、丢掉空字符串、去掉重复项,让后续界面或逻辑看到的是干净结果。

数据流:输入一个可能不存在的字符串列表 → 如果没有列表就直接返回 None;如果有,就逐个修剪空白,跳过空项,保留第一次出现的值 → 如果最后没有有效权限就返回 None,否则返回清理后的列表。

调用关系:它只被 discover_streamable_http_oauth_with_headers 调用,用在成功读到 OAuth 元数据之后。测试 discover_streamable_http_oauth_returns_normalized_scopes 和 discover_streamable_http_oauth_ignores_empty_scopes 间接验证它的行为。

调用图:被 1 处调用(discover_streamable_http_oauth_with_headers);外部调用 1 个(new)。

discovery_paths179–199 ↗
fn discovery_paths(base_path: &str) -> Vec<String>

作用:根据服务器 URL 的路径,生成几个可能的 OAuth 发现地址。因为标准允许不同摆放方式,它需要像找门牌号一样多试几个位置。

数据流:输入 URL 里的基础路径,比如 /mcp → 它去掉开头和结尾多余的斜杠,先准备标准路径 /.well-known/oauth-authorization-server;如果原路径为空,就只返回标准路径;如果不为空,就返回带原路径的几种候选地址,并避免重复 → 输出候选路径列表。

调用关系:它被 discover_streamable_http_oauth_with_headers 用来决定网络请求的尝试顺序。这样同一个探测逻辑能兼容根路径服务器,也能兼容挂在 /mcp 这类子路径下的服务器。

调用图:被 1 处调用(discover_streamable_http_oauth_with_headers);外部调用 3 个(new, format!, vec!)。

tests::TestServer::drop219–221 ↗
fn drop(&mut self)

作用:测试用的小服务器对象被丢弃时,自动关掉后台服务器任务。这样测试结束后不会留下还在跑的网络服务。

数据流:输入是即将销毁的 TestServer 自己,里面保存了后台任务句柄 → 它调用 abort 停止这个任务 → 结果是临时测试服务器停止运行,没有返回值。

调用关系:它是测试清理机制的一部分。spawn_oauth_discovery_server 创建 TestServer,测试函数用完后 Rust 自动调用这个 drop,保证测试之间互不干扰。

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

tests::spawn_oauth_discovery_server224–247 ↗
async fn spawn_oauth_discovery_server(metadata: serde_json::Value) -> TestServer

作用:启动一个只给测试用的本地 HTTP 服务器,假装自己是支持 OAuth 发现的 MCP 服务器。这样测试不用依赖真实外网服务。

数据流:输入一段 JSON 元数据 → 它在 127.0.0.1 上随机找一个空端口,建立路由 /.well-known/oauth-authorization-server/mcp,让该地址返回这段 JSON,然后把服务器放到后台运行 → 输出 TestServer,里面有可访问的 URL 和后台任务句柄。

调用关系:这是 OAuth 发现相关测试的搭台工具。discover_streamable_http_oauth_returns_normalized_scopes、discover_streamable_http_oauth_ignores_empty_scopes 和 supports_oauth_login_does_not_require_scopes_supported 都先用它造一个可控服务器,再调用正式函数检查结果。

调用图:外部调用 7 个(new, clone, get, serve, format!, bind, spawn)。

tests::EnvVarGuard::set255–264 ↗
fn set(key: &str, value: &str) -> Self

作用:测试时临时设置一个环境变量,并记住它原来的值。环境变量可以理解成操作系统给程序看的全局小纸条,测试改它时必须事后恢复。

数据流:输入环境变量名和值 → 它先读取这个变量原本是否存在、原值是什么,再把变量改成测试需要的值 → 输出 EnvVarGuard,里面保存变量名和原值,方便之后自动恢复。

调用关系:它服务于需要环境变量的测试。determine_auth_status_uses_bearer_token_when_env_authorization_header_present 用它放入临时 token;之后 tests::EnvVarGuard::drop 会负责收尾。

调用图:外部调用 2 个(set_var, var_os)。

tests::EnvVarGuard::drop268–278 ↗
fn drop(&mut self)

作用:测试结束时把被改过的环境变量恢复原状。这样一个测试不会影响后面的测试或开发者机器上的环境。

数据流:输入即将销毁的 EnvVarGuard,里面有变量名和修改前的状态 → 如果原来有值,就设回原值;如果原来没有,就删除这个变量 → 没有返回值,但会把进程环境恢复到测试前的样子。

调用关系:它和 tests::EnvVarGuard::set 配成一对。使用 guard 的测试结束后,Rust 自动调用它;相关测试还标了 serial,让涉及同一类环境变量的测试串行跑,减少互相踩踏。

调用图:外部调用 2 个(remove_var, set_var)。

tests::determine_auth_status_uses_bearer_token_when_authorization_header_present282–299 ↗
async fn determine_auth_status_uses_bearer_token_when_authorization_header_present()

作用:验证只要配置里直接带了 Authorization 请求头,认证状态就应被判定为 Bearer token。这个测试确保函数不会因为 URL 无效就去做多余的网络探测。

数据流:输入是一组测试参数,其中 HTTP 头里写了 Authorization: Bearer token,URL 故意给了 not-a-url → 它调用 determine_streamable_http_auth_status → 期望输出 McpAuthStatus::BearerToken,并用断言确认。

调用关系:这是 determine_streamable_http_auth_status 的优先级测试。它证明显式请求头比 OAuth 检查和服务器发现更早生效。

调用图:调用 2 个内部函数(default, determine_streamable_http_auth_status);外部调用 2 个(from, assert_eq!)。

tests::determine_auth_status_uses_bearer_token_when_env_authorization_header_present303–321 ↗
async fn determine_auth_status_uses_bearer_token_when_env_authorization_header_present()

作用:验证 Authorization 头的值即使来自环境变量,也会被识别为 Bearer token。这样用户把秘密 token 放进环境变量时,认证判断仍然正确。

数据流:输入是一个临时环境变量和指向该环境变量的头配置 → 它先用 EnvVarGuard::set 写入测试 token,再调用 determine_streamable_http_auth_status → 期望得到 McpAuthStatus::BearerToken;测试结束后环境变量会被恢复。

调用关系:这是 determine_streamable_http_auth_status 与 build_default_headers 配合的测试。它通过 EnvVarGuard 避免污染环境,并确认来自环境的 Authorization 也能走 Bearer token 分支。

调用图:调用 3 个内部函数(set, default, determine_streamable_http_auth_status);外部调用 2 个(from, assert_eq!)。

tests::discover_streamable_http_oauth_returns_normalized_scopes324–345 ↗
async fn discover_streamable_http_oauth_returns_normalized_scopes()

作用:验证 OAuth 发现结果里的 scopes 会被清理:去空格、去空项、去重复。这样用户看到或程序使用的权限列表不会乱糟糟。

数据流:输入是一个测试服务器返回的 JSON,里面有 authorization_endpoint、token_endpoint,以及包含重复和空白的 scopes_supported → 它启动本地测试服务器,调用 discover_streamable_http_oauth → 期望输出的 scopes_supported 只剩 profile 和 email。

调用关系:这个测试覆盖 discover_streamable_http_oauth 到 discover_streamable_http_oauth_with_headers 再到 normalize_scopes 的完整路径。它说明网络发现成功后还会做数据清洗。

调用图:调用 1 个内部函数(discover_streamable_http_oauth);外部调用 3 个(assert_eq!, spawn_oauth_discovery_server, json!)。

tests::discover_streamable_http_oauth_ignores_empty_scopes348–366 ↗
async fn discover_streamable_http_oauth_ignores_empty_scopes()

作用:验证如果服务器只返回空的权限范围,客户端会把它当成没有声明 scopes,而不是返回一堆没意义的空字符串。

数据流:输入是一个测试服务器返回的 JSON,OAuth 入口齐全,但 scopes_supported 只有空字符串和空格 → 它启动测试服务器并调用 discover_streamable_http_oauth → 期望发现成功,但 discovery.scopes_supported 是 None。

调用关系:这个测试同样走完整 OAuth 发现流程,重点检查 normalize_scopes 的边界情况:清理完为空时,要用 None 表示没有有效权限范围。

调用图:调用 1 个内部函数(discover_streamable_http_oauth);外部调用 3 个(assert_eq!, spawn_oauth_discovery_server, json!)。

tests::supports_oauth_login_does_not_require_scopes_supported369–381 ↗
async fn supports_oauth_login_does_not_require_scopes_supported()

作用:验证判断“是否支持 OAuth 登录”时,不要求服务器必须声明 scopes_supported。真正关键的是有没有授权入口和换 token 入口。

数据流:输入是一个测试服务器返回的 JSON,只包含 authorization_endpoint 和 token_endpoint → 它启动测试服务器,调用 supports_oauth_login → 期望返回 true。

调用关系:这个测试覆盖 supports_oauth_login 对 discover_streamable_http_oauth 的简化判断。它保证调用者不会因为服务器少给了可选的 scopes 字段,就误判 OAuth 不可用。

调用图:调用 1 个内部函数(supports_oauth_login);外部调用 3 个(assert!, spawn_oauth_discovery_server, json!)。

codex-mcp/src/mcp/auth.rs源码 ↗
domain_logiccross-cutting:在 MCP 登录探测、状态快照、读取 MCP 资源前后会被用到

MCP 可以理解成让 Codex 连接外部工具的一套接口。这个文件就是它的“门禁判断员”。它先看服务器的连接方式:如果是本地标准输入输出,就不支持这里的网页登录;如果是 HTTP,就继续看有没有 bearer token(一串像门票一样的访问令牌)、能不能发现 OAuth(一种不用直接交密码、通过授权页面放行的登录方式)。它还会把 OAuth 的 scope(权限范围)按优先级选出来:用户临时指定的最高,其次是配置文件写死的,再其次是服务器自己公布的,最后才是空。另一个重要部分是批量计算每个 MCP 服务器的认证状态,供状态页、资源读取等地方使用。遇到探测失败时,它不会让整个系统崩掉,而是记警告并把该服务器当成不支持认证处理。

函数细节12
oauth_login_support55–81 ↗
async fn oauth_login_support(transport: &McpServerTransportConfig) -> McpOAuthLoginSupport

作用:判断某个 MCP 连接配置能不能走 OAuth 登录。它主要给 HTTP 类型的 MCP 服务器用,看看服务器是否公开了 OAuth 登录信息。

数据流:进去的是一个 MCP 传输配置。函数先排除不是 HTTP 的配置,再排除已经要求用 bearer token 环境变量的配置;剩下的 HTTP 地址会被拿去做 OAuth 发现。出来的是三种结果之一:支持,并带上登录地址、请求头和发现到的权限范围;不支持;或者因为网络、服务器等问题导致结果未知。

调用关系:它是 OAuth 登录能力探测的第一站。discover_supported_scopes 会调用它,只取其中发现到的权限范围;它自己把真正的远程发现工作交给 discover_streamable_http_oauth。

调用图:被 1 处调用(discover_supported_scopes);外部调用 3 个(Supported, Unknown, discover_streamable_http_oauth)。

discover_supported_scopes83–90 ↗
async fn discover_supported_scopes(
    transport: &McpServerTransportConfig,
) -> Option<Vec<String>>

作用:只关心一件事:从 MCP 服务器那里找出它支持哪些 OAuth 权限范围。调用者如果不想管完整登录配置,可以用这个简化入口。

数据流:进去的是 MCP 传输配置。它调用 oauth_login_support 做完整判断;如果结果是支持 OAuth,就拿出 discovered_scopes;如果不支持或探测失败,就返回 None,表示没有可用的发现结果。

调用关系:它是 oauth_login_support 的轻量包装。上层只需要“服务器建议哪些权限”时会走这里,而不用处理支持、不支持、未知这些完整状态。

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

resolve_oauth_scopes92–124 ↗
fn resolve_oauth_scopes(
    explicit_scopes: Option<Vec<String>>,
    configured_scopes: Option<Vec<String>>,
    discovered_scopes: Option<Vec<String>>,
) -> ResolvedMcpOAuthScopes

作用:决定 OAuth 登录时最终要申请哪些 scope,也就是哪些权限范围。它解决的是“同一件事有多个来源时听谁的”这个问题。

数据流:进去有三份可能的权限列表:用户这次明确给的、配置文件里写的、服务器自动发现的。函数按固定优先级挑一份:明确给的优先,其次配置的,再其次非空的发现结果;都没有就创建一个空列表。出来的是最终权限列表,以及它来自哪里。

调用关系:它是权限选择规则的核心。多个测试函数专门检查它的优先级和空列表行为,避免以后改代码时不小心改变登录权限的选择规则。

调用图:被 5 处调用(resolve_oauth_scopes_falls_back_to_empty, resolve_oauth_scopes_prefers_configured_over_discovered, resolve_oauth_scopes_prefers_explicit, resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes, resolve_oauth_scopes_uses_discovered_when_needed);外部调用 1 个(new)。

should_retry_without_scopes126–129 ↗
fn should_retry_without_scopes(scopes: &ResolvedMcpOAuthScopes, error: &anyhow::Error) -> bool

作用:判断 OAuth 登录失败后,要不要去掉自动发现的 scope 再试一次。它是一个容错开关,专门处理服务器公布了权限但自己又拒绝这些权限的情况。

数据流:进去的是已经选好的权限来源和一次错误。函数检查两件事:这些权限是不是“服务器自动发现”的,以及错误是不是 OAuth 提供方返回的错误。两者都满足才返回 true,否则返回 false;它不修改任何状态。

调用关系:它通常会在 OAuth 授权失败后被询问。测试函数 should_retry_without_scopes_only_for_discovered_provider_errors 用几个例子确认:只有发现来的权限碰到提供方错误时才重试,用户或配置明确给的权限不会被自动丢掉。

compute_auth_statuses131–186 ↗
async fn compute_auth_statuses(
    servers: I,
    store_mode: OAuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
    auth: Option<&CodexAuth>,
) -> HashMap<String, McpAuthS

作用:一次性计算一批 MCP 服务器的认证状态。它让状态页或资源读取逻辑能快速知道每个服务器是不用登录、用令牌,还是支持其他认证方式。

数据流:进去的是服务器列表、OAuth 凭据保存方式、钥匙串后端类型,以及可选的 Codex 登录信息。函数给每个服务器准备一个异步小任务:取出配置,判断是否有运行时 Codex 认证可用,然后计算认证状态。所有任务一起跑完后,出来的是一个按服务器名索引的表;每项包含原配置和认证状态。遇到单个服务器判断失败时,会记警告,并把它标成不支持,而不是拖垮整批结果。

调用关系:它是批量认证状态计算的总调度。collect_mcp_server_status_snapshot_with_detail 和 read_mcp_resource 会调用它;它内部把“单个服务器怎么判断”这件事交给 compute_auth_status,并用 join_all 等所有服务器的异步检查一起完成。

调用图:被 2 处调用(collect_mcp_server_status_snapshot_with_detail, read_mcp_resource);外部调用 2 个(into_iter, join_all)。

compute_auth_status188–223 ↗
async fn compute_auth_status(
    server_name: &str,
    config: &McpServerConfig,
    store_mode: OAuthCredentialsStoreMode,
    keyring_backend_kind: AuthKeyringBackendKind,
    has_runtime_auth: bo

作用:判断单个 MCP 服务器当前属于哪种认证状态。它把“这个服务器是否启用、用什么传输方式、有没有运行时令牌”等信息合成一个结论。

数据流:进去的是服务器名、服务器配置、OAuth 凭据保存方式、钥匙串后端类型,以及是否已有运行时认证。函数先看服务器是否启用;没启用就返回不支持。若已有运行时认证,就直接返回 bearer token 状态。否则按传输方式判断:本地 stdio 返回不支持;HTTP 则把地址、请求头、令牌环境变量和保存设置交给底层检查函数。出来的是一个 McpAuthStatus。

调用关系:它是 compute_auth_statuses 里每个服务器实际使用的判断零件。遇到 HTTP 服务器时,它把更细的探测交给 determine_streamable_http_auth_status。

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

tests::resolve_oauth_scopes_prefers_explicit237–251 ↗
fn resolve_oauth_scopes_prefers_explicit()

作用:测试当用户明确传入 OAuth 权限时,它一定压过配置文件和服务器发现结果。这保护了“用户临时指定最优先”的规则。

数据流:进去的是三份都有值的权限列表:explicit、configured、discovered。测试调用 resolve_oauth_scopes 后,检查出来的结果只使用 explicit,并且来源标记为 Explicit。

调用关系:它是 resolve_oauth_scopes 的规则测试之一。通过 assert_eq! 对比预期结果,防止以后有人调整优先级时破坏用户明确选择。

调用图:调用 1 个内部函数(resolve_oauth_scopes);外部调用 2 个(assert_eq!, vec!)。

tests::resolve_oauth_scopes_prefers_configured_over_discovered254–268 ↗
fn resolve_oauth_scopes_prefers_configured_over_discovered()

作用:测试没有用户临时指定时,配置文件里的 OAuth 权限要优先于服务器自动发现的权限。

数据流:进去的是没有 explicit、但有 configured 和 discovered 的输入。测试调用 resolve_oauth_scopes,然后确认输出选择 configured,并把来源标成 Configured。

调用关系:它验证 resolve_oauth_scopes 的第二层优先级。这个测试说明配置文件是用户长期意图,不能被服务器发现结果随便覆盖。

调用图:调用 1 个内部函数(resolve_oauth_scopes);外部调用 2 个(assert_eq!, vec!)。

tests::resolve_oauth_scopes_uses_discovered_when_needed271–285 ↗
fn resolve_oauth_scopes_uses_discovered_when_needed()

作用:测试在用户没有指定、配置也没有写时,可以使用服务器发现到的 OAuth 权限。

数据流:进去的是只有 discovered 有值的输入。测试调用 resolve_oauth_scopes,确认输出就是 discovered,并且来源标记为 Discovered。

调用关系:它覆盖 resolve_oauth_scopes 的兜底正常路径:当没有人为设置时,才信任服务器公布的权限建议。

调用图:调用 1 个内部函数(resolve_oauth_scopes);外部调用 2 个(assert_eq!, vec!)。

tests::resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes288–302 ↗
fn resolve_oauth_scopes_preserves_explicitly_empty_configured_scopes()

作用:测试配置文件里明确写了“空权限列表”时,这个空列表也要被尊重。空不是没写,而是用户有意说“不申请权限”。

数据流:进去的是没有 explicit、configured 是空列表、discovered 有 ignored。测试调用 resolve_oauth_scopes 后,确认输出仍是空列表,来源是 Configured,而不是改用 discovered。

调用关系:它保护 resolve_oauth_scopes 一个容易被误改的细节:配置里的空列表有意义。测试里会创建空列表并用 assert_eq! 检查结果。

调用图:调用 1 个内部函数(resolve_oauth_scopes);外部调用 3 个(new, assert_eq!, vec!)。

tests::resolve_oauth_scopes_falls_back_to_empty305–318 ↗
fn resolve_oauth_scopes_falls_back_to_empty()

作用:测试所有来源都没有 OAuth 权限时,函数会安全地返回空列表,而不是报错或随便编一个权限。

数据流:进去的是三个 None。测试调用 resolve_oauth_scopes,确认出来的是空 scopes,来源标记为 Empty。

调用关系:它验证 resolve_oauth_scopes 的最后兜底路径。这样调用者可以放心拿到一个明确结果,不必自己再处理“完全没有权限信息”的混乱情况。

调用图:调用 1 个内部函数(resolve_oauth_scopes);外部调用 1 个(assert_eq!)。

tests::should_retry_without_scopes_only_for_discovered_provider_errors321–342 ↗
fn should_retry_without_scopes_only_for_discovered_provider_errors()

作用:测试 OAuth 失败后“去掉 scope 再试一次”的条件必须很严格。只有服务器自动发现的权限被 OAuth 提供方拒绝时,才允许这样重试。

数据流:测试先构造一个来源为 Discovered 的权限结果和一个 OAuthProviderError,确认 should_retry_without_scopes 返回 true。接着换成 Configured 来源,确认返回 false;再把错误换成普通超时错误,也确认返回 false。

调用关系:它直接守住 should_retry_without_scopes 的安全边界。这样程序不会把用户或配置明确指定的权限偷偷删掉,也不会对无关错误盲目重试。

调用图:外部调用 3 个(anyhow!, assert!, vec!)。

后端认证账户操作

使用适配后的后端认证机制,执行已认证的账户速率限制重置操作。

app-server/src/request_processors/account_processor/rate_limit_resets.rs源码 ↗
domain_logicrequest handling

有些账号可能拿到一种“重置限额”的额度,用户点一下就能尝试把某类使用限制清零。这个文件就是这个按钮背后的服务端入口。它先检查请求里有没有幂等键,也就是一串用来防止同一次操作被重复扣费的标记;没有就直接拒绝。然后它确认当前账号真的有合适的认证信息,能访问后端服务。接着它调用后端去“消费”这张重置额度,并给这次调用加上 10 秒超时,避免后端没反应时整个请求一直挂着。后端会返回几种结果,比如已经重置、没什么可重置、没有额度、已经兑换过。这里会把这些结果换成客户端协议里的说法再返回。调试版本里还能用环境变量把超时时间改短,方便测试超时场景。

函数细节2
AccountRequestProcessor::consume_account_rate_limit_reset_credit9–49 ↗
async fn consume_account_rate_limit_reset_credit(
        &self,
        params: ConsumeAccountRateLimitResetCreditParams,
    ) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError>

作用:这个函数处理客户端发来的“使用一次限额重置额度”请求。它负责校验请求、联系后端扣掉这次额度,并把结果包装成客户端响应。

数据流:输入是一份包含 idempotency_key 的请求参数,以及处理器自己持有的认证和配置。它先确认 idempotency_key 不是空的;然后拿到能访问后端的客户端;再在限定时间内请求后端消费这次重置额度。后端返回的代码会被翻译成“已重置、无需重置、没有额度、已经兑换过”这类结果,最后输出给客户端;如果校验失败、超时或后端报错,就输出 JSON-RPC 错误,JSON-RPC 可以理解成客户端和服务端之间用 JSON 说话的一种请求格式。

调用关系:这是外部请求进入本文件的主要处理函数。它会先把“怎么连接后端”的工作交给 AccountRequestProcessor::rate_limit_reset_backend_client,再用超时工具包住真正的后端调用,防止请求卡死。它自己不直接创建认证细节,而是依赖辅助函数准备好后端客户端。

调用图:调用 1 个内部函数(rate_limit_reset_backend_client);外部调用 2 个(var, timeout)。

AccountRequestProcessor::rate_limit_reset_backend_client51–65 ↗
async fn rate_limit_reset_backend_client(&self) -> Result<BackendClient, JSONRPCErrorError>

作用:这个函数专门准备一个能访问后端服务的客户端。它会检查用户是否已经登录,以及登录方式是否适合使用限额重置功能。

数据流:它读取处理器里的认证管理器和配置里的 chatgpt_base_url。先尝试拿当前认证;如果没有认证,就返回“需要账号认证”的错误。再检查认证是否能访问 Codex 后端;不符合就返回“需要合适认证”的错误。通过检查后,它用后端地址和认证信息创建 BackendClient;创建成功就输出这个客户端,创建失败就把失败原因包装成内部错误。

调用关系:它只在 AccountRequestProcessor::consume_account_rate_limit_reset_credit 需要联系后端前被调用。它像出门前检查证件和准备车钥匙:只有认证合格并且客户端建好后,上层函数才能继续去后端消费重置额度。实际创建客户端的底层工作交给 BackendClient::from_auth。

调用图:被 1 处调用(consume_account_rate_limit_reset_credit);外部调用 1 个(from_auth)。