Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/openshell-providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl ProviderRegistry {
registry.register(providers::generic::GenericProvider);
registry.register(providers::openai::SPEC);
registry.register(providers::anthropic::SPEC);
registry.register(providers::aws_bedrock::SPEC);
registry.register(providers::nvidia::SPEC);
registry.register(providers::gitlab::SPEC);
registry.register(providers::github::SPEC);
Expand Down
1 change: 1 addition & 0 deletions crates/openshell-providers/src/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;

const BUILT_IN_PROFILE_YAMLS: &[&str] = &[
include_str!("../../../providers/aws-bedrock.yaml"),
include_str!("../../../providers/claude-code.yaml"),
include_str!("../../../providers/github.yaml"),
include_str!("../../../providers/nvidia.yaml"),
Expand Down
20 changes: 20 additions & 0 deletions crates/openshell-providers/src/providers/aws_bedrock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::ProviderDiscoverySpec;

pub const SPEC: ProviderDiscoverySpec = ProviderDiscoverySpec {
id: "aws-bedrock",
credential_env_vars: &[
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
"AWS_SESSION_TOKEN",
"AWS_REGION",
],
};

test_discovers_env_credential!(
discovers_aws_bedrock_env_credentials,
"AWS_ACCESS_KEY_ID",
"AKIA-test-key"
);
Comment on lines +1 to +20
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove legacy provider updates, we should only be relying on providers v2 changes which should NOT require any changes to the discovery specs.

1 change: 1 addition & 0 deletions crates/openshell-providers/src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ macro_rules! test_discovers_env_credential {
};
}
pub mod anthropic;
pub mod aws_bedrock;
pub mod claude;
pub mod codex;
pub mod copilot;
Expand Down
98 changes: 97 additions & 1 deletion crates/openshell-sandbox/src/l7/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct InferenceApiPattern {
pub kind: String,
}

/// Default patterns for known inference APIs (`OpenAI`, Anthropic).
/// Default patterns for known inference APIs (`OpenAI`, Anthropic, AWS Bedrock).
pub fn default_patterns() -> Vec<InferenceApiPattern> {
vec![
InferenceApiPattern {
Expand Down Expand Up @@ -55,10 +55,31 @@ pub fn default_patterns() -> Vec<InferenceApiPattern> {
protocol: "model_discovery".to_string(),
kind: "models_get".to_string(),
},
// AWS Bedrock InvokeModel + InvokeModelWithResponseStream. The `*`
// segment is the Bedrock model id (e.g. `anthropic.claude-opus-4-7`).
InferenceApiPattern {
method: "POST".to_string(),
path_glob: "/model/*/invoke".to_string(),
protocol: "aws_bedrock_invoke".to_string(),
kind: "messages".to_string(),
},
InferenceApiPattern {
method: "POST".to_string(),
path_glob: "/model/*/invoke-with-response-stream".to_string(),
protocol: "aws_bedrock_invoke_stream".to_string(),
kind: "messages".to_string(),
},
]
}

/// Check if an HTTP request matches a known inference API pattern.
///
/// Path globs support two wildcard shapes (one per pattern, not both):
/// - **Trailing `/*`**: `/v1/models/*` matches `/v1/models` and any
/// `/v1/models/<rest>` (one or many path segments).
/// - **Middle `/*/`**: `/model/*/invoke` matches `/model/<id>/invoke`
/// for a single non-empty segment that contains no `/`. Used for
/// AWS Bedrock's `/model/{modelId}/invoke[-with-response-stream]`.
pub fn detect_inference_pattern<'a>(
method: &str,
path: &str,
Expand All @@ -78,6 +99,21 @@ pub fn detect_inference_pattern<'a>(
.is_some_and(|suffix| suffix.starts_with('/'));
}

if let Some((before, after)) = p.path_glob.split_once("/*/") {
let Some(rest) = path_only.strip_prefix(before) else {
return false;
};
let Some(rest) = rest.strip_prefix('/') else {
return false;
};
// rest must look like `<id>/<after>` where <id> is non-empty
// and contains no `/` (single path segment).
let Some(slash_at) = rest.find('/') else {
return false;
};
return slash_at > 0 && rest[slash_at + 1..] == *after;
}

path_only == p.path_glob
})
}
Expand Down Expand Up @@ -445,6 +481,66 @@ mod tests {
assert!(result.is_none());
}

#[test]
fn detect_aws_bedrock_invoke() {
let patterns = default_patterns();
let result =
detect_inference_pattern("POST", "/model/anthropic.claude-opus-4-7/invoke", &patterns);
assert!(result.is_some());
assert_eq!(result.unwrap().protocol, "aws_bedrock_invoke");
assert_eq!(result.unwrap().kind, "messages");
}

#[test]
fn detect_aws_bedrock_invoke_stream() {
let patterns = default_patterns();
let result = detect_inference_pattern(
"POST",
"/model/anthropic.claude-opus-4-7/invoke-with-response-stream",
&patterns,
);
assert!(result.is_some());
assert_eq!(result.unwrap().protocol, "aws_bedrock_invoke_stream");
}

#[test]
fn aws_bedrock_invoke_with_query_string() {
let patterns = default_patterns();
let result = detect_inference_pattern("POST", "/model/foo.bar/invoke?trace=1", &patterns);
assert!(result.is_some());
assert_eq!(result.unwrap().protocol, "aws_bedrock_invoke");
}

#[test]
fn aws_bedrock_rejects_empty_model_id() {
let patterns = default_patterns();
// `/model//invoke` — empty wildcard segment is not a valid Bedrock id.
assert!(detect_inference_pattern("POST", "/model//invoke", &patterns).is_none());
}

#[test]
fn aws_bedrock_rejects_multi_segment_model_id() {
let patterns = default_patterns();
// The `*` matches a single path segment only; multi-segment ids must
// not match (would be a path-traversal liability otherwise).
assert!(detect_inference_pattern("POST", "/model/foo/bar/invoke", &patterns).is_none());
}

#[test]
fn aws_bedrock_rejects_get() {
let patterns = default_patterns();
assert!(
detect_inference_pattern("GET", "/model/anthropic.claude-opus-4-7/invoke", &patterns)
.is_none()
);
}

#[test]
fn aws_bedrock_rejects_unknown_action() {
let patterns = default_patterns();
assert!(detect_inference_pattern("POST", "/model/foo/converse", &patterns).is_none());
}

#[test]
fn parse_simple_post_request() {
let body = b"{\"hello\":true}";
Expand Down
38 changes: 38 additions & 0 deletions providers/aws-bedrock.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

id: aws-bedrock
display_name: AWS Bedrock
description: Anthropic + Mistral + Llama models served via the AWS Bedrock InvokeModel API
category: inference
inference_capable: true
credentials:
- name: aws_access_key_id
description: AWS access key id used for SigV4 signing of outbound Bedrock requests
env_vars: [AWS_ACCESS_KEY_ID]
required: true
- name: aws_secret_access_key
description: AWS secret access key paired with aws_access_key_id
env_vars: [AWS_SECRET_ACCESS_KEY]
required: true
- name: aws_session_token
description: Optional session token for temporary credentials (STS, IAM Roles for Service Accounts)
env_vars: [AWS_SESSION_TOKEN]
required: false
- name: aws_region
description: AWS region the Bedrock endpoint resolves into (e.g. us-east-1)
env_vars: [AWS_REGION, AWS_DEFAULT_REGION]
required: true
discovery:
credentials: [aws_access_key_id, aws_secret_access_key, aws_region]
endpoints:
# Default endpoint targets us-east-1 since the YAML loader does not yet
# substitute the `{region}` placeholder. Operators in other regions
# override via the `BEDROCK_BASE_URL` config-key the same way the
# `anthropic` provider accepts `ANTHROPIC_BASE_URL`.
- host: bedrock-runtime.us-east-1.amazonaws.com
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use wildcards/glob patterns here unless you feel that is too broad of access

port: 443
protocol: rest
access: read-write
enforcement: enforce
binaries: [/usr/bin/claude, /usr/local/bin/claude]
Loading