feat: add a built-in Amazon Bedrock model provider (#18744)

## Why

Codex needs a first-class `amazon-bedrock` model provider so users can
select Bedrock without copying a full provider definition into
`config.toml`. The provider has Codex-owned defaults for the pieces that
should stay consistent across users: the display `name`, Bedrock
`base_url`, and `wire_api`.

At the same time, users still need a way to choose the AWS credential
profile used by their local environment. This change makes
`amazon-bedrock` a partially modifiable built-in provider: code owns the
provider identity and endpoint defaults, while user config can set
`model_providers.amazon-bedrock.aws.profile`.

For example:

```toml
model_provider = "amazon-bedrock"

[model_providers.amazon-bedrock.aws]
profile = "codex-bedrock"
```

## What Changed

- Added `amazon-bedrock` to the built-in model provider map with:
  - `name = "Amazon Bedrock"`
  - `base_url = "https://bedrock-mantle.us-east-1.api.aws/v1"`
  - `wire_api = "responses"`
- Added AWS provider auth config with a profile-only shape:
`model_providers.<id>.aws.profile`.
- Kept AWS auth config restricted to `amazon-bedrock`; custom providers
that set `aws` are rejected.
- Allowed `model_providers.amazon-bedrock` through reserved-provider
validation so it can act as a partial override.
- During config loading, only `aws.profile` is copied from the
user-provided `amazon-bedrock` entry onto the built-in provider. Other
Bedrock provider fields remain hard-coded by the built-in definition.
- Updated the generated config schema for the new provider AWS profile
config.
This commit is contained in:
Celia Chen
2026-04-20 17:54:05 -07:00
committed by GitHub
parent 9a2b34213b
commit cefcfe43b9
16 changed files with 461 additions and 11 deletions

View File

@@ -29,6 +29,7 @@ use crate::types::WindowsToml;
use codex_app_server_protocol::Tools;
use codex_app_server_protocol::UserSavedConfig;
use codex_features::FeaturesToml;
use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;
use codex_model_provider_info::LEGACY_OLLAMA_CHAT_PROVIDER_ID;
use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
use codex_model_provider_info::ModelProviderInfo;
@@ -56,7 +57,8 @@ use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
const RESERVED_MODEL_PROVIDER_IDS: [&str; 3] = [
const RESERVED_MODEL_PROVIDER_IDS: [&str; 4] = [
AMAZON_BEDROCK_PROVIDER_ID,
OPENAI_PROVIDER_ID,
OLLAMA_OSS_PROVIDER_ID,
LMSTUDIO_OSS_PROVIDER_ID,
@@ -780,7 +782,10 @@ pub fn validate_reserved_model_provider_ids(
) -> Result<(), String> {
let mut conflicts = model_providers
.keys()
.filter(|key| RESERVED_MODEL_PROVIDER_IDS.contains(&key.as_str()))
.filter(|key| {
key.as_str() != AMAZON_BEDROCK_PROVIDER_ID
&& RESERVED_MODEL_PROVIDER_IDS.contains(&key.as_str())
})
.map(|key| format!("`{key}`"))
.collect::<Vec<_>>();
conflicts.sort_unstable();
@@ -800,6 +805,19 @@ pub fn validate_model_providers(
) -> Result<(), String> {
validate_reserved_model_provider_ids(model_providers)?;
for (key, provider) in model_providers {
if key == AMAZON_BEDROCK_PROVIDER_ID {
continue;
}
if provider.aws.is_some() {
return Err(format!(
"model_providers.{key}: provider aws is only supported for `{AMAZON_BEDROCK_PROVIDER_ID}`"
));
}
if provider.name.trim().is_empty() {
return Err(format!(
"model_providers.{key}: provider name must not be empty"
));
}
provider
.validate()
.map_err(|message| format!("model_providers.{key}: {message}"))?;

View File

@@ -298,6 +298,7 @@ mod tests {
env_key_instructions: None,
experimental_bearer_token: None,
auth: None,
aws: None,
wire_api: WireApi::Responses,
query_params: None,
http_headers: None,