Enable Ultrafast service tiers for Amazon Bedrock Astra models (#50472)

## Why

Bedrock catalogs cleared all service-tier metadata, and requests omitted every selected tier. This prevented users from selecting `ultrafast` or using tiers defined in custom catalogs.

## What changed

- Advertise `ultrafast` for `openai.gpt-6-astra` on Mantle and the `global.openai.gpt-6-astra` and `us.openai.gpt-6-astra` Runtime variants, while retaining the implicit default tier unless users select otherwise.
- Preserve custom catalog speed tiers, service-tier definitions, and defaults while continuing to normalize Bedrock web search compatibility.
- Send selected service tiers only when the Bedrock model advertises them, including `flex`.

## Testing

Add coverage for `model/list` tier discovery, custom catalog preservation, configured and per-turn HTTP request tiers, and TUI slash-command selection, toggling, and model switching.

GitOrigin-RevId: 4793e7af7b9f604a6b7812611030da6adb8acd35
This commit is contained in:
Celia Chen
2026-10-03 00:31:51 +00:00
committed by copyberry
parent be48ae396e
commit 604061ce51
12 changed files with 527 additions and 48 deletions
@@ -0,0 +1,148 @@
//! Verifies Bedrock service-tier discovery through the public model/list API.
use app_test_support::TestAppServer;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::ModelServiceTier;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
#[test_case::test_case("amazon-bedrock", vec!["openai.gpt-6-astra"]; "mantle")]
#[test_case::test_case("amazon-bedrock-runtime", vec!["global.openai.gpt-6-astra", "us.openai.gpt-6-astra"]; "runtime")]
#[tokio::test]
async fn bedrock_model_list_advertises_ultrafast_without_changing_default(
provider_id: &str,
expected_models: Vec<&str>,
) -> anyhow::Result<()> {
let home = TempDir::new()?;
std::fs::write(
home.path().join("config.toml"),
format!(
r#"model_provider = "{provider_id}"
[model_providers.{provider_id}.aws]
region = "us-west-2"
"#
),
)?;
let mut app_server = TestAppServer::builder()
.with_codex_home(home.path())
.build_initialized()
.await?;
let response: ModelListResponse = app_server
.request(|request_id| ClientRequest::ModelList {
request_id,
params: ModelListParams {
cursor: None,
limit: Some(100),
include_hidden: Some(true),
},
})
.await?;
assert_eq!(
response
.data
.iter()
.filter(|model| !model.service_tiers.is_empty())
.map(|model| (
model.model.as_str(),
model.service_tiers.clone(),
model.default_service_tier.clone()
))
.collect::<Vec<_>>(),
expected_models
.into_iter()
.map(|model| (
model,
vec![ModelServiceTier {
id: "ultrafast".to_string(),
name: "Ultrafast".to_string(),
description: "The fastest available responses for latency-sensitive work."
.to_string(),
}],
None
))
.collect::<Vec<_>>()
);
Ok(())
}
#[test_case::test_case("amazon-bedrock", "openai.gpt-6-astra"; "mantle")]
#[test_case::test_case("amazon-bedrock-runtime", "global.openai.gpt-6-astra"; "runtime")]
#[tokio::test]
async fn bedrock_model_list_preserves_custom_service_tiers(
provider_id: &str,
model_id: &str,
) -> anyhow::Result<()> {
let home = TempDir::new()?;
let mut model = codex_models_manager::bundled_models_response()?
.models
.into_iter()
.find(|model| model.slug == "gpt-6-astra")
.expect("bundled Astra model");
model.slug = model_id.to_string();
model.additional_speed_tiers = vec!["fast".to_string()];
model.service_tiers = vec![codex_protocol::openai_models::ModelServiceTier {
id: "ultrafast".to_string(),
name: "Express".to_string(),
description: "My custom Bedrock tier.".to_string(),
}];
model.default_service_tier = Some("ultrafast".to_string());
let catalog_path = home.path().join("models.json");
std::fs::write(
&catalog_path,
serde_json::to_vec(&codex_protocol::openai_models::ModelsResponse {
models: vec![model],
})?,
)?;
let catalog_path = serde_json::to_string(&catalog_path)?;
std::fs::write(
home.path().join("config.toml"),
format!(
r#"model_provider = "{provider_id}"
model_catalog_json = {catalog_path}
[model_providers.{provider_id}.aws]
region = "us-west-2"
"#
),
)?;
let mut app_server = TestAppServer::builder()
.with_codex_home(home.path())
.build_initialized()
.await?;
let response: ModelListResponse = app_server
.request(|request_id| ClientRequest::ModelList {
request_id,
params: ModelListParams {
cursor: None,
limit: Some(100),
include_hidden: Some(true),
},
})
.await?;
assert_eq!(
response
.data
.into_iter()
.map(|model| (
model.model,
model.additional_speed_tiers,
model.service_tiers,
model.default_service_tier,
))
.collect::<Vec<_>>(),
vec![(
model_id.to_string(),
vec!["fast".to_string()],
vec![ModelServiceTier {
id: "ultrafast".to_string(),
name: "Express".to_string(),
description: "My custom Bedrock tier.".to_string(),
}],
Some("ultrafast".to_string()),
)]
);
Ok(())
}
@@ -14,6 +14,8 @@ mod auth_storage_originator;
mod auto_env;
#[path = "bedrock_gov_cloud_tests.rs"]
mod bedrock_gov_cloud;
#[path = "bedrock_service_tier_tests.rs"]
mod bedrock_service_tier;
mod bedrock_setup;
mod client_metadata;
mod code_mode_host;
+11 -6
View File
@@ -975,12 +975,17 @@ impl ModelClient {
prompt.output_schema_strict,
);
let prompt_cache_key = Some(self.prompt_cache_key(responses_metadata));
let service_tier = if self.state.provider.info().is_amazon_bedrock() {
// Bedrock only supports the implicit default tier, including with custom catalogs.
None
} else {
model_info.service_tier_for_request(service_tier)
};
let service_tier = model_info
.service_tier_for_request(service_tier)
.filter(|tier| {
// Bedrock requires an advertised tier, including for flex, which the
// generic OpenAI resolver permits without catalog support.
!self.state.provider.info().is_amazon_bedrock()
|| model_info
.service_tiers
.iter()
.any(|supported| supported.id == *tier)
});
if !include_internal {
for item in &mut input {
item.clear_tool_result_metadata();
@@ -0,0 +1,138 @@
//! Exercises Bedrock service-tier selection through provider catalogs and HTTP requests.
use codex_login::CodexAuth;
use codex_login::auth::BedrockApiKeyAuth;
use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;
use codex_model_provider_info::AMAZON_BEDROCK_RUNTIME_PROVIDER_ID;
use codex_model_provider_info::built_in_model_providers;
use codex_protocol::openai_models::ModelServiceTier;
use core_test_support::responses;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra"; "mantle")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra"; "runtime global")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "us.openai.gpt-6-astra"; "runtime us")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn bedrock_ultrafast_can_be_configured_and_toggled(
provider_id: &'static str,
model: &'static str,
) -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
let mock = responses::mount_sse_sequence(
&server,
["configured", "standard", "ultrafast"]
.into_iter()
.map(|id| responses::sse(vec![responses::ev_completed(id)]))
.collect(),
)
.await;
let test = test_codex()
.with_auth(CodexAuth::BedrockApiKey(BedrockApiKeyAuth {
api_key: "dummy".to_string(),
region: "us-west-2".to_string(),
}))
.with_model(model)
.with_config(move |config| {
let base_url = config.model_provider.base_url.clone();
config.model_provider_id = provider_id.to_string();
config.model_provider = built_in_model_providers(/*openai_base_url*/ None)
.remove(provider_id)
.expect("built-in Bedrock provider");
config.model_provider.base_url = base_url;
config.service_tier = Some("ultrafast".to_string());
})
.build_with_auto_env(&server)
.await?;
assert!(test.config.model_catalog.is_none());
test.submit_turn("configured ultrafast").await?;
test.submit_turn_with_service_tier("standard", Some("default"))
.await?;
test.submit_turn_with_service_tier("ultrafast again", Some("ultrafast"))
.await?;
assert_eq!(
mock.requests()
.iter()
.map(|request| request.body_json().get("service_tier").cloned())
.collect::<Vec<_>>(),
vec![
Some(serde_json::json!("ultrafast")),
None,
Some(serde_json::json!("ultrafast"))
]
);
Ok(())
}
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra", &["ultrafast"], Some("ultrafast"), Some("ultrafast"); "mantle astra")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra", &["ultrafast"], Some("ultrafast"), Some("ultrafast"); "runtime global astra")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "us.openai.gpt-6-astra", &["ultrafast"], Some("ultrafast"), Some("ultrafast"); "runtime us astra")]
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra", &["priority"], Some("priority"), Some("priority"); "mantle priority")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra", &["flex"], Some("flex"), Some("flex"); "runtime flex")]
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-sol", &["custom-tier"], Some("custom-tier"), Some("custom-tier"); "mantle custom tier")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-sol", &["custom-tier"], Some("custom-tier"), Some("custom-tier"); "runtime custom tier")]
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra", &[], Some("ultrafast"), None; "mantle empty tiers")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra", &[], Some("ultrafast"), None; "runtime empty tiers")]
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra", &["ultrafast"], Some("priority"), None; "mantle unadvertised tier")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra", &["ultrafast"], Some("flex"), None; "runtime unadvertised tier")]
#[test_case::test_case(AMAZON_BEDROCK_PROVIDER_ID, "openai.gpt-6-astra", &["ultrafast"], None, None; "mantle core requires explicit selection")]
#[test_case::test_case(AMAZON_BEDROCK_RUNTIME_PROVIDER_ID, "global.openai.gpt-6-astra", &["ultrafast"], None, None; "runtime core requires explicit selection")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn bedrock_custom_catalog_controls_service_tiers(
provider_id: &'static str,
model: &'static str,
advertised_tiers: &'static [&'static str],
tier: Option<&'static str>,
expected_tier: Option<&'static str>,
) -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = responses::start_mock_server().await;
let mock = responses::mount_sse_once(
&server,
responses::sse(vec![responses::ev_completed("done")]),
)
.await;
let test = test_codex()
.with_auth(CodexAuth::BedrockApiKey(BedrockApiKeyAuth {
api_key: "dummy".to_string(),
region: "us-west-2".to_string(),
}))
.with_model(model)
.with_model_info_override(model, move |info| {
info.service_tiers = advertised_tiers
.iter()
.map(|id| ModelServiceTier {
id: id.to_string(),
name: id.to_string(),
description: id.to_string(),
})
.collect();
info.default_service_tier = advertised_tiers.first().map(ToString::to_string);
})
.with_config(move |config| {
let base_url = config.model_provider.base_url.clone();
config.model_provider_id = provider_id.to_string();
config.model_provider = built_in_model_providers(/*openai_base_url*/ None)
.remove(provider_id)
.expect("built-in Bedrock provider");
config.model_provider.base_url = base_url;
config.service_tier = tier.map(str::to_string);
})
.build_with_auto_env(&server)
.await?;
test.submit_turn("hello").await?;
assert_eq!(
mock.single_request()
.body_json()
.get("service_tier")
.cloned(),
expected_tier.map(serde_json::Value::from)
);
Ok(())
}
+2
View File
@@ -57,6 +57,8 @@ mod auth_recovery_policy;
mod auto_review;
#[path = "bedrock_multi_agent_tests.rs"]
mod bedrock_multi_agent;
#[path = "bedrock_service_tier_tests.rs"]
mod bedrock_service_tier;
mod catalog_permission_messages;
mod cli_stream;
mod client;
@@ -8,10 +8,13 @@ use codex_model_provider_info::AMAZON_BEDROCK_GPT_6_LUNA_MODEL_ID;
use codex_model_provider_info::AMAZON_BEDROCK_GPT_6_SOL_MODEL_ID;
use codex_models_manager::bundled_models_response;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelServiceTier;
use codex_protocol::openai_models::ModelVisibility;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::WebSearchToolType;
use super::BedrockEndpoint;
const GPT_5_BEDROCK_CONTEXT_WINDOW: i64 = 272_000;
const GPT_5_6_SOL_OPENAI_MODEL_ID: &str = "gpt-5.6-sol";
const GPT_5_6_TERRA_OPENAI_MODEL_ID: &str = "gpt-5.6-terra";
@@ -23,7 +26,7 @@ const GPT_6_ASTRA_OPENAI_MODEL_ID: &str = "gpt-6-astra";
const GPT_5_5_OPENAI_MODEL_ID: &str = "gpt-5.5";
pub(crate) fn static_model_catalog() -> ModelsResponse {
normalize_bedrock_catalog(ModelsResponse {
let catalog = ModelsResponse {
models: vec![
bedrock_model(
bundled_openai_model(GPT_6_1_SOL_OPENAI_MODEL_ID),
@@ -74,7 +77,8 @@ pub(crate) fn static_model_catalog() -> ModelsResponse {
/*priority*/ 7,
),
],
})
};
normalize_bundled_bedrock_catalog(catalog, BedrockEndpoint::Mantle)
}
pub(super) fn static_gov_model_catalog() -> ModelsResponse {
@@ -88,12 +92,37 @@ pub(super) fn static_gov_model_catalog() -> ModelsResponse {
catalog
}
pub(crate) fn normalize_bedrock_catalog(mut catalog: ModelsResponse) -> ModelsResponse {
pub(super) fn normalize_bundled_bedrock_catalog(
mut catalog: ModelsResponse,
endpoint: BedrockEndpoint,
) -> ModelsResponse {
for model in &mut catalog.models {
// Amazon Bedrock currently only supports the implicit "default" tier for GPT models.
// Bundled defaults must not inherit OpenAI-only tiers or opt into a premium tier.
model.additional_speed_tiers.clear();
model.service_tiers.clear();
if matches!(
(endpoint, model.slug.as_str()),
(BedrockEndpoint::Mantle, AMAZON_BEDROCK_GPT_6_ASTRA_MODEL_ID)
| (
BedrockEndpoint::Runtime,
"global.openai.gpt-6-astra" | "us.openai.gpt-6-astra"
)
) {
model.service_tiers.push(ModelServiceTier {
id: "ultrafast".to_string(),
name: "Ultrafast".to_string(),
description: "The fastest available responses for latency-sensitive work."
.to_string(),
});
}
model.default_service_tier = None;
}
normalize_bedrock_catalog(catalog)
}
pub(super) fn normalize_bedrock_catalog(mut catalog: ModelsResponse) -> ModelsResponse {
// Custom catalogs own their tier definitions; only normalize wire compatibility here.
for model in &mut catalog.models {
// Bedrock rejects the `search_content_types` field used by multimodal search.
model.web_search_tool_type = WebSearchToolType::Text;
}
@@ -260,9 +289,6 @@ mod tests {
.collect::<Vec<_>>();
let mut expected = models.clone();
for model in &mut expected {
model.additional_speed_tiers.clear();
model.service_tiers.clear();
model.default_service_tier = None;
model.web_search_tool_type = WebSearchToolType::Text;
}
@@ -347,7 +373,16 @@ mod tests {
expected.use_responses_lite = false;
expected.tool_mode = None;
expected.additional_speed_tiers.clear();
expected.service_tiers.clear();
expected.service_tiers = if slug == AMAZON_BEDROCK_GPT_6_ASTRA_MODEL_ID {
vec![ModelServiceTier {
id: "ultrafast".to_string(),
name: "Ultrafast".to_string(),
description: "The fastest available responses for latency-sensitive work."
.to_string(),
}]
} else {
Vec::new()
};
expected.default_service_tier = None;
expected.web_search_tool_type = WebSearchToolType::Text;
@@ -359,12 +394,11 @@ mod tests {
}
#[test]
fn gpt_5_bedrock_models_only_allow_default_service_tier() {
fn bedrock_models_do_not_enable_priority_or_explicit_default_tiers() {
let catalog = static_model_catalog();
for model in catalog.models {
assert_eq!(model.additional_speed_tiers, Vec::<String>::new());
assert_eq!(model.service_tiers, Vec::new());
assert_eq!(model.default_service_tier, None);
assert_eq!(
model.service_tier_for_request(Some("priority".to_string())),
@@ -7,6 +7,8 @@ use codex_model_provider_info::AMAZON_BEDROCK_GPT_6_LUNA_MODEL_ID;
use codex_model_provider_info::AMAZON_BEDROCK_GPT_6_SOL_MODEL_ID;
use codex_protocol::openai_models::ModelsResponse;
use super::BedrockEndpoint;
use super::catalog::normalize_bundled_bedrock_catalog;
use super::catalog::static_model_catalog;
const ROUTING_VARIANTS: [(&str, &str, i32); 2] =
@@ -40,7 +42,7 @@ pub(super) fn static_runtime_model_catalog() -> ModelsResponse {
models.push(variant);
}
}
ModelsResponse { models }
normalize_bundled_bedrock_catalog(ModelsResponse { models }, BedrockEndpoint::Runtime)
}
#[cfg(test)]
+35 -30
View File
@@ -1298,43 +1298,48 @@ printf '%s\n' '{"AccessKeyId":"exported","SecretAccessKey":"secret"}'
}
#[tokio::test]
async fn configured_bedrock_catalog_only_allows_default_service_tier() {
let configured_model = codex_models_manager::bundled_models_response()
async fn configured_bedrock_catalog_preserves_service_tiers() {
let mut configured_model = codex_models_manager::bundled_models_response()
.expect("bundled models should parse")
.models
.into_iter()
.find(|model| model.slug == "gpt-5.5")
.expect("bundled models should include GPT-5.5");
assert!(!configured_model.additional_speed_tiers.is_empty());
assert!(!configured_model.service_tiers.is_empty());
configured_model.service_tiers = vec![codex_protocol::openai_models::ModelServiceTier {
id: "custom-tier".to_string(),
name: "Custom tier".to_string(),
description: "User-defined tier description.".to_string(),
}];
configured_model.default_service_tier = Some("custom-tier".to_string());
let configured_catalog = ModelsResponse {
models: vec![configured_model],
};
let mut expected = configured_catalog.clone();
expected.models[0].web_search_tool_type =
codex_protocol::openai_models::WebSearchToolType::Text;
let mut provider_info =
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None);
provider_info.base_url =
let mut gov_provider = ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None);
gov_provider.base_url =
Some("https://bedrock-mantle.us-gov-west-1.api.aws/openai/v1".to_string());
let provider = create_model_provider(provider_info, /*auth_manager*/ None);
let manager = provider.models_manager(
test_codex_home(),
Some(ModelsResponse {
models: vec![configured_model],
}),
);
let catalog = manager
.raw_model_catalog(
RefreshStrategy::Online,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)
.await;
assert_eq!(catalog.models.len(), 1);
assert_eq!(catalog.models[0].slug, "gpt-5.5");
assert_eq!(
catalog.models[0].additional_speed_tiers,
Vec::<String>::new()
);
assert_eq!(catalog.models[0].service_tiers, Vec::new());
assert_eq!(catalog.models[0].default_service_tier, None);
for provider_info in [
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
ModelProviderInfo::create_amazon_bedrock_runtime_provider(/*aws*/ None),
gov_provider,
] {
let provider = create_model_provider(provider_info, /*auth_manager*/ None);
for manager in [
provider.models_manager(test_codex_home(), Some(configured_catalog.clone())),
provider.models_manager_without_cache(Some(configured_catalog.clone())),
] {
let catalog = manager
.raw_model_catalog(
RefreshStrategy::Online,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)
.await;
assert_eq!(catalog, expected);
}
}
}
#[tokio::test]
@@ -0,0 +1,17 @@
---
source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "format!(\"Bundled catalog:\\n{bundled_view}\\n\\nCustom catalog:\\n{custom_view}\")"
---
Bundled catalog:
› /ultrafast The fastest available responses for latency-sensitive work.
› /ultrafast
GPT-6-Astra default · /tmp/project
Custom catalog:
› /express My custom Bedrock tier.
› /express
GPT-6-Astra default · /tmp/project
@@ -0,0 +1,17 @@
---
source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "format!(\"Bundled catalog:\\n{bundled_view}\\n\\nCustom catalog:\\n{custom_view}\")"
---
Bundled catalog:
› /ultrafast The fastest available responses for latency-sensitive work.
› /ultrafast
GPT-6-Astra (Global) default · /tmp/project
Custom catalog:
› /express My custom Bedrock tier.
› /express
GPT-6-Astra default · /tmp/project
@@ -0,0 +1,17 @@
---
source: tui/src/chatwidget/tests/bedrock_catalog_tests.rs
expression: "format!(\"Bundled catalog:\\n{bundled_view}\\n\\nCustom catalog:\\n{custom_view}\")"
---
Bundled catalog:
› /ultrafast The fastest available responses for latency-sensitive work.
› /ultrafast
GPT-6-Astra (US cross-region) default · /tmp/project
Custom catalog:
› /express My custom Bedrock tier.
› /express
GPT-6-Astra default · /tmp/project
@@ -1,4 +1,4 @@
//! Renders Bedrock provider catalogs and Astra reasoning choices.
//! Renders Bedrock catalogs and exercises Astra reasoning and service-tier choices.
use super::*;
use codex_http_client::HttpClientFactory;
@@ -115,3 +115,95 @@ async fn bedrock_govcloud_model_pickers() {
);
}
}
#[tokio::test]
async fn bedrock_ultrafast_slash_command_selects_and_clears_tier() {
for (name, provider_info, model, standard_model) in [
(
"mantle",
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
"openai.gpt-6-astra",
"openai.gpt-6-sol",
),
(
"runtime_global",
ModelProviderInfo::create_amazon_bedrock_runtime_provider(/*aws*/ None),
"global.openai.gpt-6-astra",
"global.openai.gpt-6-sol",
),
(
"runtime_us",
ModelProviderInfo::create_amazon_bedrock_runtime_provider(/*aws*/ None),
"us.openai.gpt-6-astra",
"us.openai.gpt-6-sol",
),
] {
let presets = create_model_provider(provider_info.clone(), /*auth_manager*/ None)
.models_manager_without_cache(/*config_model_catalog*/ None)
.list_models(
RefreshStrategy::Offline,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)
.await;
let (mut chat, mut events, _ops) = make_chatwidget_manual(Some(model)).await;
chat.model_catalog = Arc::new(ModelCatalog::new(presets));
chat.set_feature_enabled(Feature::FastMode, /*enabled*/ true);
chat.set_service_tier(/*service_tier*/ None);
chat.bottom_pane
.set_composer_text("/ultrafast".to_string(), Vec::new(), Vec::new());
let bundled_view = normalize_snapshot_paths(render_bottom_popup(&chat, /*width*/ 100));
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert_eq!(chat.current_service_tier(), Some("ultrafast"));
assert!(std::iter::from_fn(|| events.try_recv().ok()).any(|event| {
matches!(event, AppEvent::PersistServiceTierSelection { service_tier }
if service_tier.as_deref() == Some("ultrafast"))
}));
chat.set_model(standard_model);
assert_eq!(chat.current_service_tier(), None);
assert!(chat.current_model_service_tier_commands().is_empty());
chat.set_model(model);
assert_eq!(chat.current_service_tier(), Some("ultrafast"));
chat.bottom_pane
.set_composer_text("/ultrafast".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert_eq!(chat.current_service_tier(), Some("default"));
let mut custom_model = codex_models_manager::bundled_models_response()
.expect("bundled catalog")
.models
.into_iter()
.find(|model| model.slug == "gpt-6-astra")
.expect("bundled Astra model");
custom_model.slug = model.to_string();
custom_model.service_tiers = vec![codex_protocol::openai_models::ModelServiceTier {
id: "ultrafast".to_string(),
name: "Express".to_string(),
description: "My custom Bedrock tier.".to_string(),
}];
custom_model.default_service_tier = Some("ultrafast".to_string());
let presets = create_model_provider(provider_info, /*auth_manager*/ None)
.models_manager_without_cache(Some(codex_protocol::openai_models::ModelsResponse {
models: vec![custom_model],
}))
.list_models(
RefreshStrategy::Offline,
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)
.await;
chat.model_catalog = Arc::new(ModelCatalog::new(presets));
chat.set_service_tier(/*service_tier*/ None);
assert_eq!(chat.current_service_tier(), Some("ultrafast"));
chat.set_service_tier(Some("default".to_string()));
assert_eq!(chat.current_service_tier(), Some("default"));
chat.bottom_pane
.set_composer_text("/express".to_string(), Vec::new(), Vec::new());
let custom_view = normalize_snapshot_paths(render_bottom_popup(&chat, /*width*/ 100));
assert_chatwidget_snapshot!(
format!("bedrock_{name}_ultrafast"),
format!("Bundled catalog:\n{bundled_view}\n\nCustom catalog:\n{custom_view}")
);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert_eq!(chat.current_service_tier(), Some("ultrafast"));
}
}