diff --git a/docs/public/core-concepts/models.mdx b/docs/public/core-concepts/models.mdx index a8c3df629..34e104034 100644 --- a/docs/public/core-concepts/models.mdx +++ b/docs/public/core-concepts/models.mdx @@ -141,6 +141,16 @@ Fabro ships an [OpenRouter](/integrations/openrouter) provider definition with a enabled = true ``` +### Amazon Bedrock + +Fabro ships an [Amazon Bedrock](/integrations/bedrock) provider definition with a curated multi-vendor catalog over Bedrock's Converse API, disabled by default. Enable it and authenticate with a Bedrock API key or AWS SigV4 credentials: + +```toml title="settings.toml" +[llm.providers.bedrock] +enabled = true +base_url = "https://bedrock-runtime.us-east-1.amazonaws.com" +``` + ### Ollama Fabro ships an Ollama provider definition that is disabled by default. Enable it in settings when you want Fabro to route through a local Ollama server: diff --git a/docs/public/docs.json b/docs/public/docs.json index b2f737ba1..5dc4a9f8d 100644 --- a/docs/public/docs.json +++ b/docs/public/docs.json @@ -95,6 +95,7 @@ "integrations/github", "integrations/daytona", "integrations/litellm", + "integrations/bedrock", "integrations/openrouter", "integrations/slack", "integrations/brave-search" diff --git a/docs/public/integrations/bedrock.mdx b/docs/public/integrations/bedrock.mdx new file mode 100644 index 000000000..c8d08b6ed --- /dev/null +++ b/docs/public/integrations/bedrock.mdx @@ -0,0 +1,132 @@ +--- +title: "Amazon Bedrock" +description: "Route Fabro models through Amazon Bedrock with SigV4 or API-key auth" +--- + +[Amazon Bedrock](https://aws.amazon.com/bedrock/) hosts Anthropic, Amazon, Meta, Mistral, DeepSeek, Qwen, Moonshot, Z.AI, MiniMax, NVIDIA, and OpenAI open-weight models behind one AWS endpoint. Fabro ships a disabled `bedrock` provider entry with a curated model catalog over Bedrock's unified Converse API, so you can opt in from `settings.toml` without changing Fabro code. + +## Prerequisites + +- An AWS account with [Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) granted for the models you want +- Either a [Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html) or working AWS credentials (environment keys, profile, IMDS, IRSA, SSO) + +## Enable the provider + +Add the provider override to `~/.fabro/settings.toml`: + +```toml title="settings.toml" +_version = 1 + +[llm.providers.bedrock] +enabled = true +base_url = "https://bedrock-runtime.us-east-1.amazonaws.com" +``` + +The SigV4 signing region is derived from `base_url` — change it to your Region's endpoint (`https://bedrock-runtime..amazonaws.com`, FIPS and China endpoints included). + +## Configure credentials + +Two auth modes, tried in order: + +**Bedrock API key** (simplest): store the key and Fabro sends it as a bearer token. + +```bash +fabro secret set AWS_BEARER_TOKEN_BEDROCK bedrock-api-key-... +# or for standalone local runs +export AWS_BEARER_TOKEN_BEDROCK=bedrock-api-key-... +``` + +**AWS SigV4** (IAM-scoped): with no API key configured, Fabro signs each request using the AWS default credential chain — environment keys, shared profile, EC2/ECS instance roles, IRSA/web identity, SSO. Expiring session credentials refresh automatically. The catalog declares this as the `aws_sigv4` credential source: + +```toml +[llm.providers.bedrock.auth] +credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "aws_sigv4"] +``` + +## Included models + +The built-in catalog curates Converse-capable models, using cross-region inference profile ids (`us.`/`global.` prefixes) where on-demand access requires them: + +| Fabro model ID | Notes | +| --- | --- | +| `us.anthropic.claude-sonnet-4-6` | Provider default; Anthropic cache billing | +| `us.anthropic.claude-opus-4-8` | Anthropic cache billing | +| `us.anthropic.claude-haiku-4-5` | Provider small default | +| `us.anthropic.claude-fable-5` | Frontier; sampling params pinned by Bedrock (Fabro drops `temperature`/`top_p` automatically); requires the account-level `provider_data_share` opt-in | +| `openai.gpt-oss-120b`, `openai.gpt-oss-20b` | OpenAI open-weights | +| `amazon.nova-2-lite` | Vision | +| `meta.llama4-maverick` | Vision | +| `mistral.mistral-large-3`, `mistral.devstral-2` | | +| `deepseek.v3-2`, `qwen.qwen3-coder-next` | | +| `moonshotai.kimi-k2.5`, `zai.glm-5` | | +| `minimax.minimax-m2.5`, `nvidia.nemotron-3-super` | | + +Any other Converse-capable Bedrock model can be added as a settings model entry with `provider = "bedrock"` and the Bedrock model or inference-profile id as `api_id`. + +Not included on this provider: Claude Mythos 5 (Anthropic-Messages-only on `bedrock-mantle`, limited preview). OpenAI's frontier models live on the companion `bedrock-openai` provider below. + +## OpenAI frontier models (GPT-5.5 / GPT-5.4) + +GPT-5.5 and GPT-5.4 on Bedrock are served only by the `bedrock-mantle` endpoint's OpenAI Responses API — a different surface than Converse. Fabro ships a companion `bedrock-openai` provider for them: the same AWS account and `AWS_BEARER_TOKEN_BEDROCK` key, pointed at the mantle endpoint over the OpenAI dialect. + +```toml title="settings.toml" +[llm.providers.bedrock-openai] +enabled = true +# regional: change to https://bedrock-mantle..api.aws/openai/v1 +``` + +```bash +fabro model test --model openai.gpt-5.5 +``` + +Auth on this provider is Bedrock-API-key only (mantle SigV4 uses a different signing name than the runtime endpoint). Fabro always sends `store: false`, so nothing is retained under mantle's default 30-day response storage. + +## Use Bedrock models + +```bash +fabro model list --provider bedrock +fabro model test --model us.anthropic.claude-sonnet-4-6 +fabro run workflow.fabro --model deepseek.v3-2 +``` + +## Prompt caching + +Claude models cache automatically when the catalog row declares `prompt_cache`: Fabro places Converse `cachePoint` blocks after the system prompt, the tool definitions, and the conversation prefix — the same placement as the direct Anthropic provider. Cache reads and writes price Anthropic-style via the per-model `billing_policy`. + +## Converse extensions + +Bedrock-specific request fields pass through verbatim via `provider_options.bedrock` on API/SDK requests — the keys merge into the top level of the Converse envelope: + +```json +{ + "model": "us.anthropic.claude-sonnet-4-6", + "provider_options": { + "bedrock": { + "additionalModelRequestFields": { "top_k": 200 }, + "guardrailConfig": { "guardrailIdentifier": "gr-abc", "guardrailVersion": "1" }, + "serviceTier": { "type": "flex" } + } + } +} +``` + +## Troubleshooting + +**"no AWS credentials provider found"** — Neither an API key nor any AWS chain source resolved. Set `AWS_BEARER_TOKEN_BEDROCK`, or configure standard AWS credentials. + +**`AccessDeniedException` / 403** — The IAM principal lacks `bedrock:InvokeModel*` for the model, or model access has not been granted in the Bedrock console for your Region. + +**`ValidationException` mentioning on-demand throughput** — The model requires an inference-profile id; use the `us.`/`global.`-prefixed id from the catalog rather than the bare model id. + +**`ThrottlingException`** — Account-level Bedrock quota; consider cross-region inference profiles or a quota increase. + +## Further reading + + + + How Fabro routes model IDs, providers, and fallbacks. + + + Full reference for `[llm.providers.]` and `[llm.models.]`. + + diff --git a/docs/public/reference/user-configuration.mdx b/docs/public/reference/user-configuration.mdx index 8f4e0c38e..1cfba22b9 100644 --- a/docs/public/reference/user-configuration.mdx +++ b/docs/public/reference/user-configuration.mdx @@ -198,7 +198,7 @@ x-team-secret = { vault = "gateway_team_secret" } | `billing_policy` | `"openai"` \| `"anthropic"` \| `"gemini"` \| `"none"` | derived from `adapter` | Provider-owned billing algorithm for usage estimates. Override for exceptional providers such as local no-billing runtimes. | | `base_url` | string | built-in value or adapter runtime default | Provider API base URL. Required for most custom OpenAI-compatible providers. | | `auth` | table | omitted | API-key auth config. Omit the table entirely for providers that need no API key; any `extra_headers` are still attached. | -| `auth.credentials` | array | required when `auth` present | Ordered credential refs. Accepted forms are `vault:` and `env:`. Literal secret strings are rejected. | +| `auth.credentials` | array | required when `auth` present | Ordered credential refs. Accepted forms are `vault:`, `env:`, and `aws_sigv4` (sign requests from the AWS default credential chain — Bedrock). Literal secret strings are rejected. | | `auth.header` | `"bearer"` or `{ custom = "Header-Name" }` | `"bearer"` | Primary API-key header policy. Omit when the provider uses a standard bearer token. | | `extra_headers` | table | `{}` | Additional headers attached to provider requests. Values must be typed refs: `{ literal = "..." }`, `{ env = "NAME" }`, or `{ vault = "NAME" }`. | | `priority` | integer | `0` | Higher-priority configured providers win default selection; ties use canonical provider ID. | diff --git a/lib/crates/fabro-agent/src/cli.rs b/lib/crates/fabro-agent/src/cli.rs index ee1f05dc0..866c64733 100644 --- a/lib/crates/fabro-agent/src/cli.rs +++ b/lib/crates/fabro-agent/src/cli.rs @@ -1016,8 +1016,8 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::OpenAi), @@ -1026,7 +1026,7 @@ mod tests { let catalog = Catalog::from_builtin_with_overrides(&settings).unwrap(); let args = AgentArgs { prompt: "test".to_string(), - provider: Some("bedrock".to_string()), + provider: Some("acme-aws".to_string()), model: None, permissions: None, auto_approve: false, @@ -1037,7 +1037,7 @@ mod tests { }; let provider_id = parse_provider(&args).unwrap(); - assert_eq!(provider_id, ProviderId::new("bedrock")); + assert_eq!(provider_id, ProviderId::new("acme-aws")); assert_eq!( profile_kind_for_provider(&catalog, &provider_id, None).unwrap(), AgentProfileKind::OpenAi @@ -1049,8 +1049,8 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::OpenAi), @@ -1058,9 +1058,9 @@ mod tests { }); settings .models - .insert("bedrock-claude".to_string(), ModelCatalogSettings { - provider: Some("bedrock".to_string()), - display_name: Some("Bedrock Claude".to_string()), + .insert("acme-aws-claude".to_string(), ModelCatalogSettings { + provider: Some("acme-aws".to_string()), + display_name: Some("Acme AWS Claude".to_string()), family: Some("claude".to_string()), default: Some(true), limits: Some(SettingsModelLimits { @@ -1081,7 +1081,7 @@ mod tests { let args = AgentArgs { prompt: "test".to_string(), provider: None, - model: Some("bedrock-claude".to_string()), + model: Some("acme-aws-claude".to_string()), permissions: None, auto_approve: false, debug: false, @@ -1092,7 +1092,7 @@ mod tests { assert_eq!( resolve_provider_id(&catalog, &args).unwrap(), - ProviderId::new("bedrock") + ProviderId::new("acme-aws") ); } @@ -1101,8 +1101,8 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::OpenAi), @@ -1124,7 +1124,7 @@ mod tests { assert_eq!( resolve_provider_id(&catalog, &args).unwrap(), - ProviderId::new("bedrock") + ProviderId::new("acme-aws") ); } @@ -1133,8 +1133,8 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::OpenAi), @@ -1142,9 +1142,9 @@ mod tests { }); settings .models - .insert("bedrock-claude".to_string(), ModelCatalogSettings { - provider: Some("bedrock".to_string()), - display_name: Some("Bedrock Claude".to_string()), + .insert("acme-aws-claude".to_string(), ModelCatalogSettings { + provider: Some("acme-aws".to_string()), + display_name: Some("Acme AWS Claude".to_string()), family: Some("claude".to_string()), default: Some(true), agent_profile: Some(AgentProfileKind::Anthropic), @@ -1167,8 +1167,8 @@ mod tests { assert_eq!( profile_kind_for_provider( &catalog, - &ProviderId::new("bedrock"), - Some("bedrock-claude") + &ProviderId::new("acme-aws"), + Some("acme-aws-claude") ) .unwrap(), AgentProfileKind::Anthropic @@ -1180,20 +1180,20 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::OpenAi), ..ProviderCatalogSettings::default() }); let catalog = Catalog::from_builtin_with_overrides(&settings).unwrap(); - let provider_id = ProviderId::new("bedrock"); + let provider_id = ProviderId::new("acme-aws"); - let model_id = summarizer_model_id(&provider_id, &catalog, "bedrock-claude-sonnet-4-6"); + let model_id = summarizer_model_id(&provider_id, &catalog, "acme-aws-claude-sonnet-4-6"); assert_eq!(model_id.provider(), &provider_id); - assert_eq!(model_id.model_id(), "bedrock-claude-sonnet-4-6"); + assert_eq!(model_id.model_id(), "acme-aws-claude-sonnet-4-6"); } #[test] @@ -1201,20 +1201,20 @@ mod tests { let mut settings = LlmCatalogSettings::default(); settings .providers - .insert("bedrock".to_string(), ProviderCatalogSettings { - display_name: Some("Bedrock".to_string()), + .insert("acme-aws".to_string(), ProviderCatalogSettings { + display_name: Some("Acme AWS".to_string()), adapter: Some("openai_compatible".to_string()), base_url: Some("https://example.invalid/v1".to_string()), agent_profile: Some(AgentProfileKind::Anthropic), ..ProviderCatalogSettings::default() }); let catalog = Catalog::from_builtin_with_overrides(&settings).unwrap(); - let provider_id = ProviderId::new("bedrock"); + let provider_id = ProviderId::new("acme-aws"); - let model_id = summarizer_model_id(&provider_id, &catalog, "bedrock-claude-sonnet-4-6"); + let model_id = summarizer_model_id(&provider_id, &catalog, "acme-aws-claude-sonnet-4-6"); assert_eq!(model_id.provider(), &provider_id); - assert_eq!(model_id.model_id(), "bedrock-claude-sonnet-4-6"); + assert_eq!(model_id.model_id(), "acme-aws-claude-sonnet-4-6"); } // subagent tool registration tests diff --git a/lib/crates/fabro-auth/src/resolve.rs b/lib/crates/fabro-auth/src/resolve.rs index 96e259afd..4acae3c74 100644 --- a/lib/crates/fabro-auth/src/resolve.rs +++ b/lib/crates/fabro-auth/src/resolve.rs @@ -610,6 +610,7 @@ mod tests { r#" [providers.bedrock] adapter = "bedrock" +enabled = true base_url = "https://bedrock-runtime.eu-west-1.amazonaws.com" [providers.bedrock.auth] diff --git a/lib/crates/fabro-cli/tests/it/cmd/exec.rs b/lib/crates/fabro-cli/tests/it/cmd/exec.rs index e2a6209fc..860fb65a4 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/exec.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/exec.rs @@ -333,7 +333,7 @@ fn exec_accepts_configured_custom_provider_from_settings() { let context = test_context!(); context.write_home( ".fabro/settings.toml", - "_version = 1\n\n[llm.providers.bedrock]\nadapter = \"openai_compatible\"\nagent_profile = \"openai\"\nbase_url = \"https://bedrock.example.invalid/v1\"\n\n[llm.providers.bedrock.auth]\ncredentials = [\"env:BEDROCK_API_KEY\"]\n\n[cli.exec.model]\nprovider = \"bedrock\"\nname = \"bedrock-claude-sonnet-4-6\"\n", + "_version = 1\n\n[llm.providers.acme-aws]\nadapter = \"openai_compatible\"\nagent_profile = \"openai\"\nbase_url = \"https://bedrock.example.invalid/v1\"\n\n[llm.providers.acme-aws.auth]\ncredentials = [\"env:ACME_AWS_API_KEY\"]\n\n[cli.exec.model]\nprovider = \"acme-aws\"\nname = \"acme-claude-sonnet-4-6\"\n", ); let mut cmd = context.exec_cmd(); @@ -350,7 +350,7 @@ fn exec_accepts_configured_custom_provider_from_settings() { exit_code: 1 ----- stdout ----- ----- stderr ----- - × LLM credentials not configured for provider 'bedrock' + × LLM credentials not configured for provider 'acme-aws' "); } @@ -398,7 +398,7 @@ fn exec_server_target_accepts_configured_custom_provider_from_settings() { let context = test_context!(); context.write_home( ".fabro/settings.toml", - "_version = 1\n\n[llm.providers.bedrock]\nadapter = \"openai_compatible\"\nagent_profile = \"openai\"\nbase_url = \"https://bedrock.example.invalid/v1\"\n\n[llm.providers.bedrock.auth]\ncredentials = [\"env:BEDROCK_API_KEY\"]\n\n[cli.exec.model]\nprovider = \"bedrock\"\nname = \"bedrock-claude-sonnet-4-6\"\n", + "_version = 1\n\n[llm.providers.acme-aws]\nadapter = \"openai_compatible\"\nagent_profile = \"openai\"\nbase_url = \"https://bedrock.example.invalid/v1\"\n\n[llm.providers.acme-aws.auth]\ncredentials = [\"env:ACME_AWS_API_KEY\"]\n\n[cli.exec.model]\nprovider = \"acme-aws\"\nname = \"acme-claude-sonnet-4-6\"\n", ); let server = MockServer::start(); server.mock(|when, then| { @@ -425,7 +425,7 @@ fn exec_server_target_accepts_configured_custom_provider_from_settings() { "expected remote server failure marker, got: {stderr}" ); assert!( - !stderr.contains("unknown provider: bedrock"), + !stderr.contains("unknown provider: acme-aws"), "exec should resolve custom providers from settings for remote transport: {stderr}" ); } diff --git a/lib/crates/fabro-dev/src/commands/docs_options_reference.rs b/lib/crates/fabro-dev/src/commands/docs_options_reference.rs index d5a52370f..d04926cb1 100644 --- a/lib/crates/fabro-dev/src/commands/docs_options_reference.rs +++ b/lib/crates/fabro-dev/src/commands/docs_options_reference.rs @@ -244,7 +244,7 @@ x-team-secret = { vault = "gateway_team_secret" } | `billing_policy` | `"openai"` \| `"anthropic"` \| `"gemini"` \| `"none"` | derived from `adapter` | Provider-owned billing algorithm for usage estimates. Override for exceptional providers such as local no-billing runtimes. | | `base_url` | string | built-in value or adapter runtime default | Provider API base URL. Required for most custom OpenAI-compatible providers. | | `auth` | table | omitted | API-key auth config. Omit the table entirely for providers that need no API key; any `extra_headers` are still attached. | -| `auth.credentials` | array | required when `auth` present | Ordered credential refs. Accepted forms are `vault:` and `env:`. Literal secret strings are rejected. | +| `auth.credentials` | array | required when `auth` present | Ordered credential refs. Accepted forms are `vault:`, `env:`, and `aws_sigv4` (sign requests from the AWS default credential chain — Bedrock). Literal secret strings are rejected. | | `auth.header` | `"bearer"` or `{ custom = "Header-Name" }` | `"bearer"` | Primary API-key header policy. Omit when the provider uses a standard bearer token. | | `extra_headers` | table | `{}` | Additional headers attached to provider requests. Values must be typed refs: `{ literal = "..." }`, `{ env = "NAME" }`, or `{ vault = "NAME" }`. | | `priority` | integer | `0` | Higher-priority configured providers win default selection; ties use canonical provider ID. | diff --git a/lib/crates/fabro-llm/src/codec/bedrock_converse/decode.rs b/lib/crates/fabro-llm/src/codec/bedrock_converse/decode.rs index 117c96a88..9a37cbc35 100644 --- a/lib/crates/fabro-llm/src/codec/bedrock_converse/decode.rs +++ b/lib/crates/fabro-llm/src/codec/bedrock_converse/decode.rs @@ -150,7 +150,11 @@ pub(super) fn map_stop_reason(reason: Option<&str>) -> FinishReason { None | Some("end_turn" | "stop_sequence") => FinishReason::Stop, Some("max_tokens" | "model_context_window_exceeded") => FinishReason::Length, Some("tool_use") => FinishReason::ToolCalls, - Some("guardrail_intervened" | "content_filtered") => FinishReason::ContentFilter, + // `refusal` is the Claude 5 blocking-classifier stop, passed through + // by Bedrock for Fable-class models. + Some("guardrail_intervened" | "content_filtered" | "refusal") => { + FinishReason::ContentFilter + } Some(other) => FinishReason::Other(other.to_string()), } } @@ -193,6 +197,10 @@ mod tests { map_stop_reason(Some("content_filtered")), FinishReason::ContentFilter ); + assert_eq!( + map_stop_reason(Some("refusal")), + FinishReason::ContentFilter + ); assert_eq!( map_stop_reason(Some("malformed_tool_use")), FinishReason::Other("malformed_tool_use".to_string()) diff --git a/lib/crates/fabro-llm/src/codec/bedrock_converse/encode.rs b/lib/crates/fabro-llm/src/codec/bedrock_converse/encode.rs index ee1272c25..f158e7fb6 100644 --- a/lib/crates/fabro-llm/src/codec/bedrock_converse/encode.rs +++ b/lib/crates/fabro-llm/src/codec/bedrock_converse/encode.rs @@ -45,14 +45,25 @@ pub(super) fn encode(ctx: &CodecCtx<'_>, stream: bool) -> Result, pr #[cfg(test)] mod tests { + use fabro_model::Catalog; + use fabro_model::catalog::LlmCatalogSettings; use serde_json::json; use super::*; @@ -476,6 +489,52 @@ mod tests { assert!(encode(&ctx, false).is_err()); } + #[test] + fn sampling_params_false_drops_temperature_and_top_p() { + let settings: LlmCatalogSettings = toml::from_str( + r#" +[providers.bedrock] +adapter = "bedrock" +enabled = true +base_url = "https://bedrock-runtime.us-east-1.amazonaws.com" + +[models."pinned-model"] +provider = "bedrock" +display_name = "Pinned" +family = "claude-5" +default = true + +[models."pinned-model".limits] +context_window = 100000 + +[models."pinned-model".features] +tools = true +vision = false +reasoning = true +sampling_params = false +"#, + ) + .unwrap(); + let catalog = Catalog::from_settings(&settings).unwrap(); + + let mut request = base_request("pinned-model"); + request.top_p = Some(0.9); + let params = CodecParams::default(); + let ctx = CodecCtx { + request: &request, + provider_name: "bedrock", + deployment_id: "pinned-model", + model: catalog.get("pinned-model"), + params: ¶ms, + }; + let encoded = encode(&ctx, false).unwrap(); + + let inference = &encoded.body["inferenceConfig"]; + assert!(inference.get("temperature").is_none()); + assert!(inference.get("topP").is_none()); + assert_eq!(inference["maxTokens"], 256); + } + #[test] fn cache_points_follow_the_anthropic_placement() { let mut messages = vec![ diff --git a/lib/crates/fabro-llm/tests/integration.rs b/lib/crates/fabro-llm/tests/integration.rs index 1407f0f8c..805ff1c87 100644 --- a/lib/crates/fabro-llm/tests/integration.rs +++ b/lib/crates/fabro-llm/tests/integration.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use fabro_llm::error::ProviderErrorKind; use fabro_llm::provider::ProviderAdapter; use fabro_llm::providers::{ - AnthropicAdapter, GeminiAdapter, OpenAiAdapter, OpenAiCompatibleAdapter, + AnthropicAdapter, BedrockAdapter, GeminiAdapter, OpenAiAdapter, OpenAiCompatibleAdapter, }; use fabro_llm::types::{CostSource, FinishReason, Message, Request}; use fabro_model::Catalog; @@ -176,6 +176,69 @@ async fn gemini_complete() { assert_eq!(response.provider, "gemini"); } +#[fabro_macros::e2e_test(live("AWS_BEARER_TOKEN_BEDROCK"))] +async fn bedrock_complete_with_api_key() { + let token = std::env::var(EnvVars::AWS_BEARER_TOKEN_BEDROCK) + .expect("AWS_BEARER_TOKEN_BEDROCK must be set"); + let adapter = + BedrockAdapter::new_api_key(token, "https://bedrock-runtime.us-east-1.amazonaws.com") + .unwrap() + .with_name("bedrock"); + // Amazon Nova: first-party, no Anthropic-approval gate and no third-party + // marketplace subscription, so this runs on any Bedrock-enabled account. + let request = make_request("us.amazon.nova-2-lite-v1:0"); + let response = adapter.complete(&request).await.unwrap(); + + assert!( + !response.text().is_empty(), + "response text should not be empty" + ); + assert!(response.usage.input_tokens > 0); + assert!(response.usage.output_tokens > 0); + assert_eq!(response.provider, "bedrock"); +} + +#[fabro_macros::e2e_test(live("AWS_ACCESS_KEY_ID"))] +async fn bedrock_complete_with_sigv4() { + let adapter = BedrockAdapter::new_sigv4("https://bedrock-runtime.us-east-1.amazonaws.com") + .unwrap() + .with_name("bedrock"); + // First-party Nova — see bedrock_complete_with_api_key for why. + let request = make_request("us.amazon.nova-2-lite-v1:0"); + let response = adapter.complete(&request).await.unwrap(); + + assert!( + !response.text().is_empty(), + "response text should not be empty" + ); + assert!(response.usage.input_tokens > 0); + assert_eq!(response.provider, "bedrock"); +} + +#[fabro_macros::e2e_test(live("AWS_BEARER_TOKEN_BEDROCK"))] +async fn bedrock_openai_frontier_complete() { + let token = std::env::var(EnvVars::AWS_BEARER_TOKEN_BEDROCK) + .expect("AWS_BEARER_TOKEN_BEDROCK must be set"); + // GPT-5.x on Bedrock is the bedrock-mantle Responses surface: the plain + // openai adapter pointed at the mantle endpoint with the Bedrock key as + // the bearer token. + let adapter = OpenAiAdapter::new(token) + .with_base_url("https://bedrock-mantle.us-east-1.api.aws/openai/v1") + .with_name("bedrock-openai"); + let request = Request { + temperature: None, + ..make_request("openai.gpt-5.5") + }; + let response = adapter.complete(&request).await.unwrap(); + + assert!( + !response.text().is_empty(), + "response text should not be empty" + ); + assert!(response.usage.input_tokens > 0); + assert_eq!(response.provider, "bedrock-openai"); +} + #[fabro_macros::e2e_test(live("OPENROUTER_API_KEY"))] async fn openrouter_complete() { let api_key = diff --git a/lib/crates/fabro-model/src/catalog.rs b/lib/crates/fabro-model/src/catalog.rs index a8168b88f..947fe1f53 100644 --- a/lib/crates/fabro-model/src/catalog.rs +++ b/lib/crates/fabro-model/src/catalog.rs @@ -1982,6 +1982,112 @@ reasoning = false assert_eq!(model.provider, ProviderId::new("acme")); } + #[test] + fn builtin_bedrock_provider_is_opt_in() { + let bedrock = ProviderId::new("bedrock"); + let builtin = Catalog::builtin(); + + assert!(builtin.provider(&bedrock).is_none()); + assert!(builtin.list(Some(&bedrock)).is_empty()); + + let catalog = Catalog::from_builtin_with_overrides(&minimal_settings( + r" +[providers.bedrock] +enabled = true +", + )) + .expect("enabled Bedrock override should build from the built-in provider settings"); + + let provider = catalog + .provider(&bedrock) + .expect("enabled Bedrock provider should be present"); + assert_eq!(provider.adapter, AdapterKind::Bedrock); + assert_eq!(provider.codec, CodecKind::BedrockConverse); + assert_eq!( + provider.base_url.as_deref(), + Some("https://bedrock-runtime.us-east-1.amazonaws.com") + ); + // Bearer key first, SigV4 chain as the fallback. + assert_eq!(provider.auth.as_ref().unwrap().credentials, vec![ + CredentialRef::Env("AWS_BEARER_TOKEN_BEDROCK".to_string()), + CredentialRef::AwsSigv4, + ]); + + // Claude rows bill Anthropic-style; open-weights rows override the + // provider's Anthropic defaults the other way. + assert_eq!( + catalog + .model_settings("us.anthropic.claude-sonnet-4-6") + .unwrap() + .billing_policy, + BillingPolicy::Anthropic + ); + assert_eq!( + catalog.model_settings("zai.glm-5").unwrap().billing_policy, + BillingPolicy::OpenAi + ); + assert_eq!( + catalog + .model_settings("us.anthropic.claude-haiku-4-5") + .unwrap() + .api_id, + "us.anthropic.claude-haiku-4-5-20251001-v1:0" + ); + assert_eq!( + catalog + .default_for_provider(&bedrock) + .map(|model| model.id.as_str()), + Some("us.anthropic.claude-sonnet-4-6") + ); + // Fable 5 ships with sampling params pinned off (the Converse + // encoder drops temperature/top_p for it). + let fable = catalog + .get("us.anthropic.claude-fable-5") + .expect("fable row should be present"); + assert!(!fable.features.sampling_params); + assert_eq!( + catalog + .model_settings("us.anthropic.claude-fable-5") + .unwrap() + .billing_policy, + BillingPolicy::Anthropic + ); + } + + #[test] + fn builtin_bedrock_openai_provider_is_opt_in() { + let provider_id = ProviderId::new("bedrock-openai"); + let builtin = Catalog::builtin(); + + assert!(builtin.provider(&provider_id).is_none()); + + let catalog = Catalog::from_builtin_with_overrides(&minimal_settings( + r" +[providers.bedrock-openai] +enabled = true +", + )) + .expect("enabled bedrock-openai override should build"); + + let provider = catalog + .provider(&provider_id) + .expect("enabled bedrock-openai provider should be present"); + // OpenAI frontier on Bedrock rides the existing openai_responses + // dialect against the bedrock-mantle endpoint — pure configuration. + assert_eq!(provider.adapter, AdapterKind::OpenAi); + assert_eq!(provider.codec, CodecKind::OpenAiResponses); + assert_eq!( + provider.base_url.as_deref(), + Some("https://bedrock-mantle.us-east-1.api.aws/openai/v1") + ); + assert_eq!( + catalog + .default_for_provider(&provider_id) + .map(|model| model.id.as_str()), + Some("openai.gpt-5.5") + ); + } + #[test] fn builtin_openrouter_provider_is_opt_in() { let openrouter = ProviderId::new("openrouter"); diff --git a/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml b/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml new file mode 100644 index 000000000..bcba55ef9 --- /dev/null +++ b/lib/crates/fabro-model/src/catalog/providers/bedrock-openai.toml @@ -0,0 +1,70 @@ +[providers.bedrock-openai] +display_name = "Amazon Bedrock (OpenAI frontier)" +adapter = "openai" +api_key_url = "https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html" +base_url = "https://bedrock-mantle.us-east-1.api.aws/openai/v1" +priority = 19 +enabled = false + +[providers.bedrock-openai.auth] +credentials = ["env:AWS_BEARER_TOKEN_BEDROCK"] + +# OpenAI's frontier models on Bedrock (GPT-5.5/5.4) are served ONLY by the +# bedrock-mantle endpoint's OpenAI Responses API — they are not reachable +# through Converse or InvokeModel on bedrock-runtime. That surface speaks +# the openai_responses dialect with a Bedrock API key as the bearer token, +# so this companion provider row is pure configuration over the existing +# openai adapter: same AWS account and key as the `bedrock` provider, a +# different endpoint and wire dialect. +# +# Notes: +# - Auth is Bedrock-API-key only on this row (SigV4 on mantle uses the +# `bedrock-mantle` signing name, which the openai adapter does not do). +# - bedrock-mantle is regional (13 regions); change base_url to +# `https://bedrock-mantle..api.aws/openai/v1` as needed. +# - Responses state: Fabro always sends `store: false`, so nothing is +# retained under mantle's default 30-day Project retention. +# +# To enable, add to ~/.fabro/settings.toml: +# +# [llm.providers.bedrock-openai] +# enabled = true + +[models."openai.gpt-5.5"] +provider = "bedrock-openai" +display_name = "GPT-5.5 (Bedrock)" +family = "gpt-5" +default = true + +[models."openai.gpt-5.5".limits] +context_window = 272000 +max_output = 128000 + +[models."openai.gpt-5.5".features] +tools = true +vision = true +reasoning = true +reasoning_effort = "levels" + +[models."openai.gpt-5.5".costs] +input_cost_per_mtok = 5.5 +output_cost_per_mtok = 33.0 + +[models."openai.gpt-5.4"] +provider = "bedrock-openai" +display_name = "GPT-5.4 (Bedrock)" +family = "gpt-5" + +[models."openai.gpt-5.4".limits] +context_window = 272000 +max_output = 128000 + +[models."openai.gpt-5.4".features] +tools = true +vision = true +reasoning = true +reasoning_effort = "levels" + +[models."openai.gpt-5.4".costs] +input_cost_per_mtok = 2.75 +output_cost_per_mtok = 16.5 diff --git a/lib/crates/fabro-model/src/catalog/providers/bedrock.toml b/lib/crates/fabro-model/src/catalog/providers/bedrock.toml new file mode 100644 index 000000000..d1fb9298f --- /dev/null +++ b/lib/crates/fabro-model/src/catalog/providers/bedrock.toml @@ -0,0 +1,372 @@ +[providers.bedrock] +display_name = "Amazon Bedrock" +adapter = "bedrock" +api_key_url = "https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html" +base_url = "https://bedrock-runtime.us-east-1.amazonaws.com" +priority = 20 +enabled = false + +[providers.bedrock.auth] +# An explicit Bedrock API key wins; SigV4 (the AWS default credential +# chain, resolved at request time) is the fallback. `aws_sigv4` always +# resolves, which is why this provider ships disabled: enabling it is the +# operator's statement that AWS credentials are expected to work. +credentials = ["env:AWS_BEARER_TOKEN_BEDROCK", "aws_sigv4"] + +# To enable Bedrock, add the following to ~/.fabro/settings.toml: +# +# [llm.providers.bedrock] +# enabled = true +# base_url = "https://bedrock-runtime..amazonaws.com" +# +# The signing region is derived from the base_url. Authenticate with +# either a Bedrock API key (AWS_BEARER_TOKEN_BEDROCK) or any AWS default +# credential chain source (env keys, profile, IMDS, IRSA, SSO). +# +# Model ids use cross-region inference profiles (`us.` / `global.` +# prefixes) where on-demand access requires them. Pricing rows are +# best-effort estimates from June 2026 list prices. + +# ---------- Anthropic Claude ---------- +# +# Claude bills Anthropic-style cache reads/writes, so these rows override +# the provider's billing default. Claude Fable 5 is deliberately absent: +# its Bedrock deployment pins sampling parameters (temperature must be +# unset) that the Converse route does not gate yet — a named follow-up. + +[models."us.anthropic.claude-sonnet-4-6"] +provider = "bedrock" +display_name = "Claude Sonnet 4.6 (Bedrock)" +family = "claude-4" +billing_policy = "anthropic" +default = true + +[models."us.anthropic.claude-sonnet-4-6".limits] +context_window = 1000000 +max_output = 64000 + +[models."us.anthropic.claude-sonnet-4-6".features] +tools = true +vision = true +reasoning = true +prompt_cache = true + +[models."us.anthropic.claude-sonnet-4-6".costs] +input_cost_per_mtok = 3.0 +output_cost_per_mtok = 15.0 +cache_input_cost_per_mtok = 0.3 + +[models."us.anthropic.claude-opus-4-8"] +provider = "bedrock" +display_name = "Claude Opus 4.8 (Bedrock)" +family = "claude-4" +billing_policy = "anthropic" + +[models."us.anthropic.claude-opus-4-8".limits] +context_window = 1000000 +max_output = 128000 + +[models."us.anthropic.claude-opus-4-8".features] +tools = true +vision = true +reasoning = true +prompt_cache = true + +[models."us.anthropic.claude-opus-4-8".costs] +input_cost_per_mtok = 5.0 +output_cost_per_mtok = 25.0 +cache_input_cost_per_mtok = 0.5 + +[models."us.anthropic.claude-haiku-4-5"] +provider = "bedrock" +api_id = "us.anthropic.claude-haiku-4-5-20251001-v1:0" +display_name = "Claude Haiku 4.5 (Bedrock)" +family = "claude-4" +billing_policy = "anthropic" +small_default = true + +[models."us.anthropic.claude-haiku-4-5".limits] +context_window = 200000 +max_output = 64000 + +[models."us.anthropic.claude-haiku-4-5".features] +tools = true +vision = true +reasoning = false +prompt_cache = true + +[models."us.anthropic.claude-haiku-4-5".costs] +input_cost_per_mtok = 1.0 +output_cost_per_mtok = 5.0 +cache_input_cost_per_mtok = 0.1 + +# ---------- OpenAI open-weights ---------- +# +# GPT-5.5/5.4 are NOT here: on Bedrock they are Responses-API-only on the +# bedrock-mantle endpoint (no Converse), a named follow-up route. + +[models."openai.gpt-oss-120b"] +provider = "bedrock" +api_id = "openai.gpt-oss-120b-1:0" +display_name = "GPT-OSS 120B (Bedrock)" +family = "gpt-oss" +billing_policy = "openai" +agent_profile = "openai" + +[models."openai.gpt-oss-120b".limits] +context_window = 128000 +max_output = 16384 + +[models."openai.gpt-oss-120b".features] +tools = true +vision = false +reasoning = true + +[models."openai.gpt-oss-120b".costs] +input_cost_per_mtok = 0.15 +output_cost_per_mtok = 0.60 + +[models."openai.gpt-oss-20b"] +provider = "bedrock" +api_id = "openai.gpt-oss-20b-1:0" +display_name = "GPT-OSS 20B (Bedrock)" +family = "gpt-oss" +billing_policy = "openai" +agent_profile = "openai" + +[models."openai.gpt-oss-20b".limits] +context_window = 128000 +max_output = 16384 + +[models."openai.gpt-oss-20b".features] +tools = true +vision = false +reasoning = true + +[models."openai.gpt-oss-20b".costs] +input_cost_per_mtok = 0.07 +output_cost_per_mtok = 0.30 + +# ---------- Amazon Nova ---------- + +[models."amazon.nova-2-lite"] +provider = "bedrock" +api_id = "global.amazon.nova-2-lite-v1:0" +display_name = "Nova 2 Lite (Bedrock)" +family = "nova-2" +billing_policy = "openai" +agent_profile = "openai" + +[models."amazon.nova-2-lite".limits] +context_window = 1000000 +max_output = 65536 + +[models."amazon.nova-2-lite".features] +tools = true +vision = true +reasoning = false + +[models."amazon.nova-2-lite".costs] +input_cost_per_mtok = 0.30 +output_cost_per_mtok = 2.50 + +# ---------- Open-weights ---------- + +[models."meta.llama4-maverick"] +provider = "bedrock" +api_id = "us.meta.llama4-maverick-17b-instruct-v1:0" +display_name = "Llama 4 Maverick (Bedrock)" +family = "llama-4" +billing_policy = "openai" +agent_profile = "openai" + +[models."meta.llama4-maverick".limits] +context_window = 1000000 +max_output = 8192 + +[models."meta.llama4-maverick".features] +tools = true +vision = true +reasoning = false + +[models."mistral.mistral-large-3"] +provider = "bedrock" +api_id = "mistral.mistral-large-3-675b-instruct" +display_name = "Mistral Large 3 (Bedrock)" +family = "mistral-large" +billing_policy = "openai" +agent_profile = "openai" + +[models."mistral.mistral-large-3".limits] +context_window = 256000 +max_output = 32768 + +[models."mistral.mistral-large-3".features] +tools = true +vision = true +reasoning = false + +[models."mistral.mistral-large-3".costs] +input_cost_per_mtok = 0.50 +output_cost_per_mtok = 1.50 + +[models."mistral.devstral-2"] +provider = "bedrock" +api_id = "mistral.devstral-2-123b" +display_name = "Devstral 2 (Bedrock)" +family = "devstral" +billing_policy = "openai" +agent_profile = "openai" + +[models."mistral.devstral-2".limits] +context_window = 256000 +max_output = 32768 + +[models."mistral.devstral-2".features] +tools = true +vision = false +reasoning = false + +[models."deepseek.v3-2"] +provider = "bedrock" +api_id = "deepseek.v3.2" +display_name = "DeepSeek V3.2 (Bedrock)" +family = "deepseek-v3" +billing_policy = "openai" +agent_profile = "openai" + +[models."deepseek.v3-2".limits] +context_window = 164000 +max_output = 8192 + +[models."deepseek.v3-2".features] +tools = true +vision = false +reasoning = true + +[models."deepseek.v3-2".costs] +input_cost_per_mtok = 0.62 +output_cost_per_mtok = 1.85 + +[models."qwen.qwen3-coder-next"] +provider = "bedrock" +display_name = "Qwen3 Coder Next (Bedrock)" +family = "qwen3" +billing_policy = "openai" +agent_profile = "openai" + +[models."qwen.qwen3-coder-next".limits] +context_window = 256000 +max_output = 16384 + +[models."qwen.qwen3-coder-next".features] +tools = true +vision = false +reasoning = false + +[models."moonshotai.kimi-k2.5"] +provider = "bedrock" +display_name = "Kimi K2.5 (Bedrock)" +family = "kimi-k2" +billing_policy = "openai" +agent_profile = "openai" + +[models."moonshotai.kimi-k2.5".limits] +context_window = 262144 +max_output = 16384 + +[models."moonshotai.kimi-k2.5".features] +tools = true +vision = true +reasoning = false + +[models."moonshotai.kimi-k2.5".costs] +input_cost_per_mtok = 0.60 +output_cost_per_mtok = 3.00 + +[models."zai.glm-5"] +provider = "bedrock" +display_name = "GLM 5 (Bedrock)" +family = "glm" +billing_policy = "openai" +agent_profile = "openai" + +[models."zai.glm-5".limits] +context_window = 200000 +max_output = 128000 + +[models."zai.glm-5".features] +tools = true +vision = false +reasoning = false + +[models."zai.glm-5".costs] +input_cost_per_mtok = 1.00 +output_cost_per_mtok = 3.20 + +[models."minimax.minimax-m2.5"] +provider = "bedrock" +display_name = "MiniMax M2.5 (Bedrock)" +family = "minimax-m2" +billing_policy = "openai" +agent_profile = "openai" + +[models."minimax.minimax-m2.5".limits] +context_window = 196000 +max_output = 8192 + +[models."minimax.minimax-m2.5".features] +tools = true +vision = false +reasoning = false + +[models."minimax.minimax-m2.5".costs] +input_cost_per_mtok = 0.30 +output_cost_per_mtok = 1.20 + +[models."nvidia.nemotron-3-super"] +provider = "bedrock" +api_id = "nvidia.nemotron-super-3-120b" +display_name = "Nemotron 3 Super (Bedrock)" +family = "nemotron-3" +billing_policy = "openai" +agent_profile = "openai" + +[models."nvidia.nemotron-3-super".limits] +context_window = 256000 +max_output = 32768 + +[models."nvidia.nemotron-3-super".features] +tools = true +vision = false +reasoning = false + +# Claude Fable 5: adaptive thinking is always on server-side; the row pins +# sampling_params = false so the Converse encoder drops temperature/top_p +# (Bedrock rejects them for this model). Requires the account-level +# provider_data_share opt-in in the Bedrock console. Effort-level mapping +# through additionalModelRequestFields is a named follow-up, so +# reasoning_effort stays undeclared here (requests carrying one are +# rejected up front rather than silently dropped). + +[models."us.anthropic.claude-fable-5"] +provider = "bedrock" +display_name = "Claude Fable 5 (Bedrock)" +family = "claude-5" +billing_policy = "anthropic" + +[models."us.anthropic.claude-fable-5".limits] +context_window = 1000000 +max_output = 128000 + +[models."us.anthropic.claude-fable-5".features] +tools = true +vision = true +reasoning = true +prompt_cache = true +sampling_params = false + +[models."us.anthropic.claude-fable-5".costs] +input_cost_per_mtok = 10.0 +output_cost_per_mtok = 50.0 +cache_input_cost_per_mtok = 1.0 diff --git a/lib/crates/fabro-redact/data/gitleaks.toml b/lib/crates/fabro-redact/data/gitleaks.toml index 00b7cb9ca..c61cdec2c 100644 --- a/lib/crates/fabro-redact/data/gitleaks.toml +++ b/lib/crates/fabro-redact/data/gitleaks.toml @@ -2546,6 +2546,20 @@ regex = '''\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\"|\n|\r|\s|\x60|; entropy = 3 keywords = ["t3blbkfj"] +[[rules]] +id = "aws-bedrock-long-term-api-key" +description = "Found an AWS Bedrock long-term API key, posing a risk of unauthorized model invocation and billing." +regex = '''\b(ABSK[A-Za-z0-9+/]{109,269}={0,2})(?:[\x60'\"\s;]|\\[nr]|$)''' +entropy = 3 +keywords = ["absk"] + +[[rules]] +id = "aws-bedrock-short-term-api-key" +description = "Found an AWS Bedrock short-term API key, posing a risk of unauthorized model invocation." +regex = '''\b(bedrock-api-key-YmVkcm9jay5hbWF6b25hd3MuY29t[A-Za-z0-9+/=]{16,})(?:[\x60'\"\s;]|\\[nr]|$)''' +entropy = 3 +keywords = ["bedrock-api-key-"] + [[rules]] id = "openrouter-api-key" description = "Found an OpenRouter API Key, posing a risk of unauthorized access to LLM provider routing and billing." diff --git a/lib/crates/fabro-static/src/env_vars.rs b/lib/crates/fabro-static/src/env_vars.rs index 315de6477..a5ff25b29 100644 --- a/lib/crates/fabro-static/src/env_vars.rs +++ b/lib/crates/fabro-static/src/env_vars.rs @@ -40,6 +40,7 @@ impl EnvVars { // LLM providers and tool integrations pub const ANTHROPIC_API_KEY: &'static str = "ANTHROPIC_API_KEY"; + pub const AWS_BEARER_TOKEN_BEDROCK: &'static str = "AWS_BEARER_TOKEN_BEDROCK"; pub const ANTHROPIC_BASE_URL: &'static str = "ANTHROPIC_BASE_URL"; pub const BRAVE_SEARCH_API_KEY: &'static str = "BRAVE_SEARCH_API_KEY"; pub const CHATGPT_ACCOUNT_ID: &'static str = "CHATGPT_ACCOUNT_ID"; @@ -179,6 +180,7 @@ mod tests { EnvVars::FABRO_WORKER_TOKEN, EnvVars::ANTHROPIC_API_KEY, EnvVars::ANTHROPIC_BASE_URL, + EnvVars::AWS_BEARER_TOKEN_BEDROCK, EnvVars::BRAVE_SEARCH_API_KEY, EnvVars::CHATGPT_ACCOUNT_ID, EnvVars::GEMINI_API_KEY, diff --git a/lib/crates/fabro-static/src/secret_registry.rs b/lib/crates/fabro-static/src/secret_registry.rs index b6770aeab..adc9a693f 100644 --- a/lib/crates/fabro-static/src/secret_registry.rs +++ b/lib/crates/fabro-static/src/secret_registry.rs @@ -16,6 +16,7 @@ const BOOTSTRAP_SECRETS: &[&str] = &[ const OPTIONAL_VAULT_SECRETS: &[&str] = &[ EnvVars::ANTHROPIC_API_KEY, + EnvVars::AWS_BEARER_TOKEN_BEDROCK, EnvVars::BRAVE_SEARCH_API_KEY, EnvVars::FABRO_SLACK_APP_TOKEN, EnvVars::FABRO_SLACK_BOT_TOKEN, @@ -86,6 +87,7 @@ mod tests { EnvVars::DAYTONA_API_KEY, EnvVars::BRAVE_SEARCH_API_KEY, EnvVars::ANTHROPIC_API_KEY, + EnvVars::AWS_BEARER_TOKEN_BEDROCK, EnvVars::GEMINI_API_KEY, EnvVars::INCEPTION_API_KEY, EnvVars::KIMI_API_KEY,