feat: add Bedrock API key as a managed auth mode (#27443)

## Why

Codex needs to manage Amazon Bedrock API key credentials through the
existing auth lifecycle instead of introducing a separate auth manager
or provider-specific credential file. Treating Bedrock API key login as
a primary auth mode gives it the same persistence, keyring, reload, and
logout behavior as the existing OpenAI API key and ChatGPT modes.

The credential is valid only for the `amazon-bedrock` model provider.
OpenAI-compatible providers must reject this auth mode rather than
treating the Bedrock key as an OpenAI bearer token.

## What changed

- Added `bedrockApiKey` as an app-server `AuthMode` and
`CodexAuth::BedrockApiKey` as a primary `AuthManager` mode.
- Added `BedrockApiKeyAuth`, containing the API key and AWS region, to
the existing `AuthDotJson` payload stored in `$CODEX_HOME/auth.json` or
the configured keyring backend.
- Added `login_with_bedrock_api_key(...)`, parallel to
`login_with_api_key(...)`, which replaces the current stored login with
Bedrock credentials.
- Reused generic auth reload and logout behavior instead of adding a
Bedrock-specific auth manager or logout path.
- Updated login restrictions, status reporting, diagnostics, telemetry
classification, generated app-server schemas, and auth fixtures for the
new mode.
- Added explicit errors when Bedrock API key auth is selected with an
OpenAI-compatible model provider.

This PR establishes managed storage and auth-mode behavior. Routing the
managed key and region into Amazon Bedrock requests will be in follow-up
PRs.
This commit is contained in:
Celia Chen authored and GitHub committed 2026-06-10 20:42:38 -07:00
1 parent 87ab01834a
commit 06afd63f4a
30 files changed
+426 -15

No files matched your search

@@ -56,6 +56,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -121,6 +122,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -187,6 +189,7 @@ async fn auth_refreshes_when_access_token_is_near_expiry() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -238,6 +241,7 @@ async fn auth_skips_access_token_outside_refresh_window() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -275,6 +279,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -286,6 +291,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -342,6 +348,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -354,6 +361,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -414,6 +422,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -463,6 +472,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> {
last_refresh: Some(fresh_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -514,6 +524,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -526,6 +537,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
last_refresh: Some(fresh_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -579,6 +591,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
last_refresh: Some(stale_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -591,6 +604,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
last_refresh: Some(fresh_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -642,6 +656,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -696,6 +711,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -764,6 +780,7 @@ async fn refresh_token_does_not_retry_after_bad_request_reused_failure() -> Resu
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -832,6 +849,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -855,6 +873,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
last_refresh: Some(fresh_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -915,6 +934,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()>
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -969,6 +989,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -980,6 +1001,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -1065,6 +1087,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&initial_auth).await?;
@@ -1077,6 +1100,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
save_auth(
ctx.codex_home.path(),
@@ -1136,6 +1160,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> {
last_refresh: None,
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
};
ctx.write_auth(&auth).await?;
+1
View File
@@ -196,6 +196,7 @@ fn chatgpt_auth_with_refresh_token(refresh_token: &str) -> AuthDotJson {
last_refresh: None,
agent_identity: None,
personal_access_token: None,
bedrock_api_key: None,
}
}