Integrate Bedrock model catalog metadata

This commit is contained in:
2026-08-22 12:51:31 -05:00
parent f291cfe803
commit f17642fc62
24 changed files with 27662 additions and 26 deletions
Generated
+9
View File
@@ -5748,6 +5748,7 @@ dependencies = [
"galaxy_acp",
"galaxy_agent_core",
"galaxy_agent_rig",
"galaxy_bedrock_model_catalog",
"galaxy_cli",
"galaxy_completer",
"galaxy_core",
@@ -5993,6 +5994,14 @@ dependencies = [
"uuid",
]
[[package]]
name = "galaxy_bedrock_model_catalog"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "galaxy_cli"
version = "0.0.0"
+1
View File
@@ -31,6 +31,7 @@ publish = false
galaxy_acp = { path = "crates/acp" }
galaxy_agent_core = { path = "crates/galaxy_agent_core" }
galaxy_agent_rig = { path = "crates/galaxy_agent_rig" }
galaxy_bedrock_model_catalog = { path = "crates/galaxy_bedrock_model_catalog" }
ai = { path = "crates/ai" }
app-installation-detection = { path = "crates/app-installation-detection" }
asset_cache = { path = "crates/asset_cache" }
+1
View File
@@ -210,6 +210,7 @@ galaxy_completer.workspace = true
galaxy_core.workspace = true
galaxy_agent_core.workspace = true
galaxy_agent_rig.workspace = true
galaxy_bedrock_model_catalog.workspace = true
galaxy_editor.workspace = true
galaxy_graphql.workspace = true
galaxy_js = { workspace = true, optional = true }
+5 -1
View File
@@ -413,10 +413,14 @@ impl RequestParams {
// server-side, drop the override; otherwise clamp it to the model's
// current `[min, max]` range. This closes the window between an
// in-flight model metadata refresh and the next request.
let llm_preferences = LLMPreferences::as_ref(app);
let selected_model = llm_preferences
.get_llm_info(&request_input.model_id)
.unwrap_or_else(|| llm_preferences.get_active_base_model(app, terminal_view_id));
let context_window_limit = AIExecutionProfilesModel::as_ref(app)
.active_profile(terminal_view_id, app)
.data()
.context_window_limit_for_request(app);
.context_window_limit_for_model_request(selected_model, app);
Self {
terminal_view_id,
+10 -6
View File
@@ -8,6 +8,7 @@ use aws_config::BehaviorVersion;
use aws_sdk_bedrock::Client;
use aws_sdk_bedrockruntime::config::Region;
use futures::{stream, StreamExt};
use galaxy_bedrock_model_catalog::model_metadata;
use super::client::{BedrockClientConfig, BedrockError};
use crate::settings::ai::BedrockModelConfig;
@@ -71,20 +72,23 @@ pub async fn discover_available_models(
return None;
}
let display_name = summary
.model_name()
.map(str::to_owned)
.unwrap_or_else(|| prettify_model_id(model_id));
let metadata = model_metadata(model_id);
let display_name = summary.model_name().map(str::to_owned).unwrap_or_else(|| {
metadata
.map(|metadata| metadata.model_name.clone())
.unwrap_or_else(|| prettify_model_id(model_id))
});
let vision_supported = summary
.input_modalities()
.iter()
.any(|modality| modality.as_str() == "IMAGE");
.any(|modality| modality.as_str() == "IMAGE")
|| metadata.is_some_and(|metadata| metadata.supports_vision_input());
Some(BedrockModelConfig {
model_id: model_id.to_owned(),
display_name,
vision_supported,
use_rig: false,
use_rig: true,
})
}
});
@@ -2050,7 +2050,7 @@ impl AgentInputFooter {
let profile_context = AIExecutionProfilesModel::as_ref(ctx)
.active_profile(Some(self.terminal_view_id), ctx)
.data()
.context_window_display_value(ctx);
.context_window_display_value_for_model(active_model, ctx);
let model_max_context = active_model
.context_window
.default_max
+48 -6
View File
@@ -117,7 +117,17 @@ pub trait AIExecutionProfileAppExt {
fn configurable_context_window(&self, app: &AppContext) -> Option<LLMContextWindow>;
fn context_window_display_value(&self, app: &AppContext) -> Option<u32>;
fn context_window_display_value_for_model(
&self,
model: &LLMInfo,
app: &AppContext,
) -> Option<u32>;
fn context_window_limit_for_request(&self, app: &AppContext) -> Option<u32>;
fn context_window_limit_for_model_request(
&self,
model: &LLMInfo,
app: &AppContext,
) -> Option<u32>;
fn should_show_long_context_pricing_warning(
&self,
context_window_limit: Option<u32>,
@@ -139,20 +149,52 @@ impl AIExecutionProfileAppExt for AIExecutionProfile {
}
fn context_window_display_value(&self, app: &AppContext) -> Option<u32> {
let cw = self.configurable_context_window(app)?;
Some(self.context_window_limit.unwrap_or(cw.default_max))
self.context_window_display_value_for_model(effective_base_model(self, app), app)
}
fn context_window_limit_for_request(&self, app: &AppContext) -> Option<u32> {
let llm = effective_base_model(self, app);
fn context_window_display_value_for_model(
&self,
model: &LLMInfo,
app: &AppContext,
) -> Option<u32> {
if !has_configurable_context_window(
llm,
model,
FeatureFlag::GPTConfigurableContextWindow.is_enabled(),
) {
return None;
}
let selected_limit = if effective_base_model(self, app).id == model.id {
self.context_window_limit
} else {
None
};
Some(
selected_limit
.unwrap_or(model.context_window.default_max)
.clamp(model.context_window.min, model.context_window.max),
)
}
fn context_window_limit_for_request(&self, app: &AppContext) -> Option<u32> {
self.context_window_limit_for_model_request(effective_base_model(self, app), app)
}
fn context_window_limit_for_model_request(
&self,
model: &LLMInfo,
app: &AppContext,
) -> Option<u32> {
if !has_configurable_context_window(
model,
FeatureFlag::GPTConfigurableContextWindow.is_enabled(),
) || effective_base_model(self, app).id != model.id
{
return None;
}
self.context_window_limit
.map(|limit| limit.clamp(llm.context_window.min, llm.context_window.max))
.map(|limit| limit.clamp(model.context_window.min, model.context_window.max))
}
fn should_show_long_context_pricing_warning(
+23 -2
View File
@@ -11,6 +11,8 @@ use galaxy_agent_rig::{
discover_anthropic_models, discover_gemini_models, validate_vertex_ai_credentials,
vertex_ai_model_catalog, RigModelInfo,
};
#[cfg(not(target_family = "wasm"))]
use galaxy_bedrock_model_catalog::model_metadata;
use galaxy_core::features::FeatureFlag;
use galaxy_core::ui::icons::Icon;
use galaxy_core::user_preferences::GetUserPreferences;
@@ -873,11 +875,29 @@ impl LLMPreferences {
let effective = effective;
for model in effective {
let metadata = model_metadata(&model.model_id);
if metadata.is_some_and(|metadata| metadata.supports_agent_runtime() == Some(false)) {
log::debug!(
"[bedrock] Excluding {} from Agent Mode because its catalog metadata marks it as incompatible with streaming Converse",
model.model_id
);
continue;
}
let model_id = if cross_region && !region.is_empty() {
super::bedrock::models::apply_cross_region_prefix(&model.model_id, &region)
} else {
model.model_id.clone()
};
let context_window = metadata
.and_then(|metadata| metadata.context_window_tokens)
.map(|tokens| LLMContextWindow {
is_configurable: false,
min: tokens,
max: tokens,
default_max: tokens,
})
.unwrap_or_default();
let llm_info = LLMInfo {
id: LLMId::from(model_id.as_str()),
@@ -890,7 +910,8 @@ impl LLMPreferences {
},
description: Some("AWS Bedrock".to_string()),
disable_reason: None,
vision_supported: model.vision_supported,
vision_supported: model.vision_supported
|| metadata.is_some_and(|metadata| metadata.supports_vision_input()),
spec: None,
provider: LLMProvider::Bedrock,
host_configs: HashMap::from([(
@@ -901,7 +922,7 @@ impl LLMPreferences {
},
)]),
discount_percentage: None,
context_window: LLMContextWindow::default(),
context_window,
};
self.models_by_feature
.agent_mode
+39 -10
View File
@@ -8,6 +8,7 @@ use galaxy_agent_rig::{
GeminiRuntimeConfig, OpenAICompatibleRuntime, OpenAICompatibleRuntimeConfig, VertexAiRuntime,
VertexAiRuntimeConfig,
};
use galaxy_bedrock_model_catalog::model_metadata;
use uuid::Uuid;
use warp_multi_agent_api::ToolType;
@@ -79,7 +80,11 @@ pub(crate) async fn prepare_provider_run(
let (supported_tools, supported_cli_agent_tools) =
crate::ai::agent::api::prepare_direct_provider_params(&mut params);
let skill_path_origin = params.session_context.skill_path_origin();
let max_context_tokens = params.context_window_limit;
let max_context_tokens = provider_context_window_tokens(
&base_provider_config,
params.model.as_str(),
params.context_window_limit,
);
let mut cli_params = params.clone();
let cli_model_is_placeholder = params.cli_agent_model.as_str().trim().is_empty()
|| params
@@ -186,14 +191,17 @@ async fn prepare_provider_profile(
),
None => prepare_rig_turn(config, params, supported_tools, supported_cli_agent_tools),
},
crate::ai::provider::ProviderConfig::Bedrock(_) => prepare_bedrock_rig_turn_for_mode(
model,
Some(64_000),
params,
supported_tools,
supported_cli_agent_tools,
mode,
),
crate::ai::provider::ProviderConfig::Bedrock(_) => {
let max_output_tokens = Some(bedrock_max_output_tokens(&model));
prepare_bedrock_rig_turn_for_mode(
model,
max_output_tokens,
params,
supported_tools,
supported_cli_agent_tools,
mode,
)
}
crate::ai::provider::ProviderConfig::None => {
anyhow::bail!(
"No AI runtime configured. Enable an agent runtime or model provider in settings."
@@ -250,7 +258,7 @@ pub(crate) async fn provider_runtime_for_request(
})),
},
crate::ai::provider::ProviderConfig::Bedrock(config) => {
let max_output_tokens = Some(64_000);
let max_output_tokens = Some(bedrock_max_output_tokens(&model));
let cross_region_inference = config.cross_region_inference;
let caching_config =
CachingConfig::from_external_config(&ExternalBedrockConfig::load());
@@ -275,6 +283,27 @@ pub(crate) async fn provider_runtime_for_request(
Ok(runtime)
}
fn bedrock_max_output_tokens(model: &str) -> u64 {
model_metadata(model)
.and_then(|metadata| metadata.max_output_tokens)
.map(u64::from)
.unwrap_or(64_000)
}
fn provider_context_window_tokens(
provider_config: &crate::ai::provider::ProviderConfig,
model: &str,
configured_limit: Option<u32>,
) -> Option<u32> {
configured_limit.or_else(|| match provider_config {
crate::ai::provider::ProviderConfig::Bedrock(_) => {
model_metadata(model).and_then(|metadata| metadata.context_window_tokens)
}
crate::ai::provider::ProviderConfig::OpenAI(_)
| crate::ai::provider::ProviderConfig::None => None,
})
}
#[cfg(test)]
#[path = "rig_tests.rs"]
mod tests;
@@ -0,0 +1,10 @@
[package]
name = "galaxy_bedrock_model_catalog"
version = "0.1.0"
edition = "2024"
publish.workspace = true
license.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
@@ -0,0 +1,20 @@
# Galaxy Bedrock Model Catalog
This crate embeds an offline snapshot of the public Amazon Bedrock Models
Catalog. It intentionally exposes the upstream JSON verbatim so Galaxy can
adopt newly added fields without changing the snapshot layer first.
Refresh the snapshot from the workspace root:
```sh
./script/update_bedrock_model_catalog
```
The updater resolves the current upstream `main` commit, downloads all catalog
documents at that immutable commit, validates their JSON shapes, and updates
`data/UPSTREAM_COMMIT`.
Normal Cargo builds never require network access. The checked-in snapshot is
embedded with `include_str!` and rebuilt whenever one of the data files changes.
See [UPSTREAM.md](UPSTREAM.md) for provenance and licensing notes.
@@ -0,0 +1,14 @@
# Upstream provenance
- Source: https://github.com/amazonbedrockmodels/amazonbedrockmodels.github.io
- Data directory: https://github.com/amazonbedrockmodels/amazonbedrockmodels.github.io/tree/main/data
- Snapshot commit: recorded in `data/UPSTREAM_COMMIT`
- Upstream license declaration: MIT, as stated in the upstream README
The upstream project is an unofficial, community-maintained catalog refreshed
from AWS Bedrock APIs. Its data is useful for discovery and enrichment, but AWS
documentation and live account/region responses remain authoritative for model
availability and access.
The JSON files in `data/` are preserved verbatim from upstream. Refresh them
with `./script/update_bedrock_model_catalog` rather than editing them manually.
@@ -0,0 +1 @@
df190def1e3301a479a386f8eeaddd5f49bef7a4
@@ -0,0 +1,10 @@
{
"version": "1.0",
"description": "Public API for the Amazon Bedrock Models Catalog",
"endpoints": {
"models": "/data/models.json",
"profiles": "/data/profiles.json",
"beta_models": "/data/beta_models.json"
},
"usage": "Fetch any of the endpoints above to get the latest model data in JSON format."
}
@@ -0,0 +1,12 @@
[
{
"id": "writer.palmyra-vision-7b",
"name": "Writer Palmyra Vision 7B",
"provider": "Writer"
},
{
"id": "zai.glm-4.6",
"name": "GLM 4.6",
"provider": "Z.AI"
}
]
@@ -0,0 +1,739 @@
{
"anthropic.claude-fable-5": {
"created": 1780272000,
"regions": [
"us-east-1"
]
},
"anthropic.claude-haiku-4-5": {
"created": 1759276800,
"regions": [
"ap-northeast-1",
"eu-north-1",
"eu-west-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"anthropic.claude-opus-4-7": {
"created": 1775692800,
"regions": [
"ap-northeast-1",
"eu-north-1",
"eu-west-1",
"us-east-1"
]
},
"anthropic.claude-opus-4-8": {
"created": 1779148800,
"regions": [
"ap-northeast-1",
"eu-north-1",
"eu-west-1",
"us-east-1"
]
},
"anthropic.claude-opus-5": {
"created": 1782950400,
"regions": [
"eu-north-1",
"eu-west-1",
"us-east-1"
]
},
"anthropic.claude-sonnet-5": {
"created": 1781654400,
"regions": [
"eu-north-1",
"eu-west-1",
"us-east-1"
]
},
"deepseek.v3.1": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"deepseek.v3.2": {
"created": 1769385600,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-3-12b-it": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-3-27b-it": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-3-4b-it": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-4-26b-a4b": {
"created": 1777852800,
"regions": [
"eu-central-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-4-31b": {
"created": 1778112000,
"regions": [
"eu-central-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"google.gemma-4-e2b": {
"created": 1778112000,
"regions": [
"eu-central-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"minimax.minimax-m2": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"minimax.minimax-m2.1": {
"created": 1769396433,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"minimax.minimax-m2.5": {
"created": 1769396433,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.devstral-2-123b": {
"created": 1765843200,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.magistral-small-2509": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.ministral-3-14b-instruct": {
"created": 1763923865,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.ministral-3-3b-instruct": {
"created": 1763923654,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.ministral-3-8b-instruct": {
"created": 1763923750,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.mistral-large-3-675b-instruct": {
"created": 1763923896,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.voxtral-mini-3b-2507": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"mistral.voxtral-small-24b-2507": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"moonshotai.kimi-k2-thinking": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"moonshotai.kimi-k2.5": {
"created": 1769558400,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"nvidia.nemotron-nano-12b-v2": {
"created": 1763769600,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"nvidia.nemotron-nano-3-30b": {
"created": 1765065600,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"nvidia.nemotron-nano-9b-v2": {
"created": 1763769600,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"nvidia.nemotron-super-3-120b": {
"created": 1768780800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-5.4": {
"created": 1777507200,
"regions": [
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-5.4-2026-03-05": {
"created": 1777507200,
"regions": [
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-5.5": {
"created": 1779321600,
"regions": [
"us-east-1",
"us-east-2"
]
},
"openai.gpt-5.5-2026-04-23": {
"created": 1779321600,
"regions": [
"us-east-1",
"us-east-2"
]
},
"openai.gpt-5.6-luna": {
"created": 1782345600,
"regions": [
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-5.6-sol": {
"created": 1781827200,
"regions": [
"us-east-1",
"us-east-2"
]
},
"openai.gpt-5.6-terra": {
"created": 1781827200,
"regions": [
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-oss-120b": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-oss-20b": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-oss-safeguard-120b": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"openai.gpt-oss-safeguard-20b": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-235b-a22b-2507": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-32b": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-coder-30b-a3b-instruct": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-coder-480b-a35b-instruct": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-coder-next": {
"created": 1770163200,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-next-80b-a3b-instruct": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"qwen.qwen3-vl-235b-a22b-instruct": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"writer.palmyra-vision-7b": {
"created": 1771804800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"xai.grok-4.3": {
"created": 1778630400,
"regions": [
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"xai.grok-4.6": {
"created": 1786492800,
"regions": [
"us-west-2"
]
},
"zai.glm-4.6": {
"created": 1764460800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"zai.glm-4.7": {
"created": 1769558400,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"zai.glm-4.7-flash": {
"created": 1769644800,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
},
"zai.glm-5": {
"created": 1771770206,
"regions": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-2",
"eu-north-1",
"eu-west-2",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
]
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
//! A versioned, offline snapshot of the public Amazon Bedrock model catalog.
//!
//! The JSON is kept verbatim so consumers can adopt new upstream fields without
//! first changing this crate. Run `./script/update_bedrock_model_catalog` from
//! the workspace root to refresh the snapshot.
mod metadata;
pub use metadata::{ModelMetadata, model_metadata, models};
/// Repository from which the bundled snapshot was downloaded.
pub const UPSTREAM_REPOSITORY: &str =
"https://github.com/amazonbedrockmodels/amazonbedrockmodels.github.io";
/// The catalog's public API manifest.
pub const API_MANIFEST_JSON: &str = include_str!("../data/api.json");
/// Foundation models discovered through the AWS-native Bedrock APIs.
pub const FOUNDATION_MODELS_JSON: &str = include_str!("../data/models.json");
/// System-defined inference profiles discovered across AWS regions.
pub const INFERENCE_PROFILES_JSON: &str = include_str!("../data/profiles.json");
/// Models discovered before they appear in the main AWS documentation.
pub const BETA_MODELS_JSON: &str = include_str!("../data/beta_models.json");
/// Models available through the Bedrock Mantle endpoint.
pub const MANTLE_MODELS_JSON: &str = include_str!("../data/mantle_models.json");
/// Enriched model-card metadata maintained by the upstream catalog.
pub const MODEL_CARDS_JSON: &str = include_str!("../data/model_cards.json");
/// Returns the exact upstream commit used for the bundled snapshot.
pub fn upstream_commit() -> &'static str {
include_str!("../data/UPSTREAM_COMMIT").trim()
}
/// All catalog documents embedded in this crate.
#[derive(Debug, Clone, Copy)]
pub struct CatalogSnapshot {
pub api_manifest: &'static str,
pub foundation_models: &'static str,
pub inference_profiles: &'static str,
pub beta_models: &'static str,
pub mantle_models: &'static str,
pub model_cards: &'static str,
}
/// The bundled catalog snapshot.
pub const SNAPSHOT: CatalogSnapshot = CatalogSnapshot {
api_manifest: API_MANIFEST_JSON,
foundation_models: FOUNDATION_MODELS_JSON,
inference_profiles: INFERENCE_PROFILES_JSON,
beta_models: BETA_MODELS_JSON,
mantle_models: MANTLE_MODELS_JSON,
model_cards: MODEL_CARDS_JSON,
};
#[cfg(test)]
#[path = "lib_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "metadata_tests.rs"]
mod metadata_tests;
@@ -0,0 +1,42 @@
use serde_json::Value;
use super::*;
#[test]
fn bundled_catalog_documents_are_valid_json() {
for document in [
API_MANIFEST_JSON,
FOUNDATION_MODELS_JSON,
INFERENCE_PROFILES_JSON,
BETA_MODELS_JSON,
MANTLE_MODELS_JSON,
MODEL_CARDS_JSON,
] {
serde_json::from_str::<Value>(document).expect("catalog document should be valid JSON");
}
}
#[test]
fn bundled_catalog_has_models_and_provenance() {
let foundation_models: Value =
serde_json::from_str(FOUNDATION_MODELS_JSON).expect("foundation models should parse");
let mantle_models: Value =
serde_json::from_str(MANTLE_MODELS_JSON).expect("Mantle models should parse");
assert!(
foundation_models
.as_array()
.is_some_and(|models| !models.is_empty())
);
assert!(
mantle_models
.as_object()
.is_some_and(|models| !models.is_empty())
);
assert_eq!(upstream_commit().len(), 40);
assert!(
upstream_commit()
.bytes()
.all(|byte| byte.is_ascii_hexdigit())
);
}
@@ -0,0 +1,228 @@
use std::collections::HashMap;
use std::sync::OnceLock;
use serde::Deserialize;
use crate::FOUNDATION_MODELS_JSON;
const CROSS_REGION_PREFIXES: &[&str] = &["global.", "apac.", "us.", "eu.", "jp.", "au."];
/// Provider-neutral metadata for one Bedrock foundation model.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ModelMetadata {
pub model_id: String,
pub model_name: String,
pub provider_name: String,
pub input_modalities: Vec<String>,
pub output_modalities: Vec<String>,
pub response_streaming_supported: Option<bool>,
pub regions: Vec<String>,
pub context_window_tokens: Option<u32>,
pub max_output_tokens: Option<u32>,
pub converse_supported: Option<bool>,
pub invoke_supported: Option<bool>,
pub bedrock_runtime_supported: Option<bool>,
pub bedrock_mantle_supported: Option<bool>,
pub mantle_regions: Vec<String>,
pub model_card_url: Option<String>,
}
impl ModelMetadata {
pub fn supports_vision_input(&self) -> bool {
self.input_modalities
.iter()
.any(|modality| modality == "IMAGE")
}
/// Reports whether the catalog has enough evidence to classify this as an
/// Agent Mode model. `None` deliberately leaves new or incomplete catalog
/// entries usable until their metadata catches up.
pub fn supports_agent_runtime(&self) -> Option<bool> {
if !self.output_modalities.is_empty()
&& !self
.output_modalities
.iter()
.any(|modality| modality == "TEXT")
{
return Some(false);
}
for support in [
self.response_streaming_supported,
self.converse_supported,
self.bedrock_runtime_supported,
] {
if support == Some(false) {
return Some(false);
}
}
if self.output_modalities.is_empty()
|| self.response_streaming_supported.is_none()
|| self.converse_supported.is_none()
|| self.bedrock_runtime_supported.is_none()
{
None
} else {
Some(true)
}
}
}
/// Returns all parsed foundation-model metadata in the bundled snapshot.
pub fn models() -> &'static [ModelMetadata] {
&catalog_index().models
}
/// Finds metadata for a foundation model, accepting Bedrock ARNs,
/// cross-region inference prefixes, and Galaxy's optional `[1m]` suffix.
pub fn model_metadata(model_id: &str) -> Option<&'static ModelMetadata> {
let index = catalog_index();
let normalized = normalize_model_id(model_id);
index
.by_id
.get(normalized)
.and_then(|position| index.models.get(*position))
}
fn catalog_index() -> &'static CatalogIndex {
static INDEX: OnceLock<CatalogIndex> = OnceLock::new();
INDEX.get_or_init(CatalogIndex::load)
}
struct CatalogIndex {
models: Vec<ModelMetadata>,
by_id: HashMap<String, usize>,
}
impl CatalogIndex {
fn load() -> Self {
let raw_models =
serde_json::from_str::<Vec<RawModel>>(FOUNDATION_MODELS_JSON).unwrap_or_default();
let models = raw_models
.into_iter()
.map(ModelMetadata::from)
.collect::<Vec<_>>();
let by_id = models
.iter()
.enumerate()
.map(|(position, model)| (model.model_id.clone(), position))
.collect();
Self { models, by_id }
}
}
impl From<RawModel> for ModelMetadata {
fn from(raw: RawModel) -> Self {
let card = raw.model_card.unwrap_or_default();
Self {
model_id: raw.model_id,
model_name: raw.model_name,
provider_name: raw.provider_name,
input_modalities: raw.input_modalities,
output_modalities: raw.output_modalities,
response_streaming_supported: raw.response_streaming_supported,
regions: raw.regions,
context_window_tokens: card.context_window.as_deref().and_then(parse_token_count),
max_output_tokens: card
.max_output_tokens
.as_deref()
.and_then(parse_token_count),
converse_supported: card.apis_supported.as_ref().and_then(|apis| apis.converse),
invoke_supported: card.apis_supported.as_ref().and_then(|apis| apis.invoke),
bedrock_runtime_supported: card
.endpoints_supported
.as_ref()
.and_then(|endpoints| endpoints.bedrock_runtime),
bedrock_mantle_supported: card
.endpoints_supported
.as_ref()
.and_then(|endpoints| endpoints.bedrock_mantle),
mantle_regions: card.mantle_regions,
model_card_url: card.model_card_url,
}
}
}
fn normalize_model_id(model_id: &str) -> &str {
let model_id = model_id.rsplit('/').next().unwrap_or(model_id);
let model_id = model_id
.strip_suffix("[1m]")
.or_else(|| model_id.strip_suffix("[1M]"))
.unwrap_or(model_id);
CROSS_REGION_PREFIXES
.iter()
.find_map(|prefix| model_id.strip_prefix(prefix))
.unwrap_or(model_id)
}
fn parse_token_count(value: &str) -> Option<u32> {
let value = value
.trim()
.strip_suffix("tokens")
.or_else(|| value.trim().strip_suffix("token"))
.unwrap_or(value)
.trim();
let suffix_start = value
.find(|character: char| !character.is_ascii_digit() && character != ',' && character != '.')
.unwrap_or(value.len());
let (number, suffix) = value.split_at(suffix_start);
let multiplier = match suffix.trim().to_ascii_uppercase().as_str() {
"" => 1.0,
"K" => 1_000.0,
"M" => 1_000_000.0,
"B" => 1_000_000_000.0,
_ => return None,
};
let number = number.replace(',', "").parse::<f64>().ok()?;
let tokens = number * multiplier;
if !tokens.is_finite() || tokens < 0.0 || tokens > u32::MAX as f64 {
return None;
}
Some(tokens.round() as u32)
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawModel {
model_id: String,
#[serde(default)]
model_name: String,
#[serde(default)]
provider_name: String,
#[serde(default)]
input_modalities: Vec<String>,
#[serde(default)]
output_modalities: Vec<String>,
#[serde(default)]
response_streaming_supported: Option<bool>,
#[serde(default)]
regions: Vec<String>,
#[serde(default)]
model_card: Option<RawModelCard>,
}
#[derive(Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawModelCard {
context_window: Option<String>,
max_output_tokens: Option<String>,
apis_supported: Option<RawApisSupported>,
endpoints_supported: Option<RawEndpointsSupported>,
#[serde(default)]
mantle_regions: Vec<String>,
model_card_url: Option<String>,
}
#[derive(Deserialize)]
struct RawApisSupported {
converse: Option<bool>,
invoke: Option<bool>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawEndpointsSupported {
bedrock_runtime: Option<bool>,
bedrock_mantle: Option<bool>,
}
@@ -0,0 +1,50 @@
use super::*;
#[test]
fn reads_context_and_capabilities_for_an_agent_model() {
let metadata = model_metadata("anthropic.claude-opus-4-8").unwrap();
assert_eq!(metadata.model_name, "Claude Opus 4.8");
assert_eq!(metadata.provider_name, "Anthropic");
assert_eq!(metadata.context_window_tokens, Some(1_000_000));
assert_eq!(metadata.max_output_tokens, Some(128_000));
assert!(metadata.supports_vision_input());
assert_eq!(metadata.supports_agent_runtime(), Some(true));
}
#[test]
fn normalizes_inference_profile_ids_and_galaxy_suffixes() {
let metadata = model_metadata(
"arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-opus-4-8[1M]",
)
.unwrap();
assert_eq!(metadata.model_id, "anthropic.claude-opus-4-8");
}
#[test]
fn preserves_unknown_and_invalid_metadata_as_unknown() {
assert!(model_metadata("example.future-model-v1").is_none());
assert_eq!(
model_metadata("cohere.embed-english-v3")
.unwrap()
.context_window_tokens,
None
);
}
#[test]
fn rejects_known_non_agent_models() {
assert_eq!(
model_metadata("cohere.embed-english-v3")
.unwrap()
.supports_agent_runtime(),
Some(false)
);
assert_eq!(
model_metadata("amazon.nova-2-sonic-v1:0")
.unwrap()
.supports_agent_runtime(),
Some(false)
);
}
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Refresh Galaxy's checked-in Amazon Bedrock model catalog snapshot."""
from __future__ import annotations
import json
import subprocess
import sys
import tempfile
import urllib.error
import urllib.request
from pathlib import Path
UPSTREAM_GIT_URL = (
"https://github.com/amazonbedrockmodels/amazonbedrockmodels.github.io.git"
)
UPSTREAM_RAW_URL = (
"https://raw.githubusercontent.com/amazonbedrockmodels/"
"amazonbedrockmodels.github.io"
)
UPSTREAM_REF = "refs/heads/main"
CATALOG_FILES = {
"api.json": dict,
"beta_models.json": list,
"mantle_models.json": dict,
"model_cards.json": dict,
"models.json": list,
"profiles.json": list,
}
def resolve_upstream_commit() -> str:
result = subprocess.run(
["git", "ls-remote", UPSTREAM_GIT_URL, UPSTREAM_REF],
check=True,
capture_output=True,
text=True,
timeout=30,
)
fields = result.stdout.split()
if len(fields) != 2 or fields[1] != UPSTREAM_REF or len(fields[0]) != 40:
raise RuntimeError(f"unexpected git ls-remote response: {result.stdout!r}")
return fields[0]
def download_file(commit: str, filename: str) -> bytes:
url = f"{UPSTREAM_RAW_URL}/{commit}/data/{filename}"
request = urllib.request.Request(url, headers={"User-Agent": "Galaxy catalog updater"})
with urllib.request.urlopen(request, timeout=30) as response:
if response.status != 200:
raise RuntimeError(f"downloading {filename} returned HTTP {response.status}")
return response.read()
def validate_document(filename: str, contents: bytes, expected_type: type) -> None:
try:
document = json.loads(contents)
except (UnicodeDecodeError, json.JSONDecodeError) as error:
raise RuntimeError(f"{filename} is not valid UTF-8 JSON: {error}") from error
if not isinstance(document, expected_type):
raise RuntimeError(
f"{filename} has type {type(document).__name__}; "
f"expected {expected_type.__name__}"
)
def main() -> int:
workspace_root = Path(__file__).resolve().parent.parent
destination = workspace_root / "crates" / "galaxy_bedrock_model_catalog" / "data"
commit_file = destination / "UPSTREAM_COMMIT"
print("Resolving the latest Amazon Bedrock model catalog commit...")
commit = resolve_upstream_commit()
if commit_file.exists() and commit_file.read_text().strip() == commit:
print(f"Catalog is already current at {commit}")
return 0
with tempfile.TemporaryDirectory(prefix="galaxy-bedrock-catalog-") as temp_dir:
staged_directory = Path(temp_dir)
for filename, expected_type in CATALOG_FILES.items():
print(f"Downloading {filename}...")
contents = download_file(commit, filename)
validate_document(filename, contents, expected_type)
(staged_directory / filename).write_bytes(contents)
destination.mkdir(parents=True, exist_ok=True)
for filename in CATALOG_FILES:
(staged_directory / filename).replace(destination / filename)
commit_file.write_text(f"{commit}\n")
print(f"Updated Galaxy's Bedrock model catalog to {commit}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, RuntimeError, subprocess.SubprocessError, urllib.error.URLError) as error:
print(f"error: {error}", file=sys.stderr)
raise SystemExit(1) from error