mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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]
|
||||
|
||||
+17
@@ -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
|
||||
+17
@@ -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
|
||||
+17
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user