feat: add AWS Bedrock AgentCore browser provider (native Rust) (#397)

* feat: add AWS Bedrock AgentCore browser provider (native Rust)

- Add agentcore provider with SigV4 authentication
- AWS SDK deps are optional behind 'agentcore' feature flag
- Build with: cargo build --features agentcore
- Supports AGENTCORE_REGION, AGENTCORE_PROFILE_ID, AGENTCORE_BROWSER_ID env vars
- Returns session ID and Live View URL in launch response
- Add connect_cdp_with_headers for signed WebSocket connections

* test: add unit tests for AgentCore provider

* refactor: use lightweight manual SigV4 signing instead of AWS SDK

- Replace aws-sigv4/aws-config with manual HMAC-SHA256 signing
- Removes ~60s compile time and significant binary size
- Credentials read from AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars
- Supports AWS_SESSION_TOKEN for temporary credentials

* fix: correct AgentCore API endpoints

- Host: bedrock-agentcore.{region}.amazonaws.com
- Start session: PUT /browsers/{id}/sessions/start
- Stop session: PUT /browsers/{id}/sessions/stop
- Add urlencoding for browser ID in path
- Add AWS_DEFAULT_REGION fallback

* fix: use profileConfiguration.profileIdentifier for AgentCore profile

The AWS Bedrock AgentCore API expects profile configuration in the format:
{
  "profileConfiguration": {
    "profileIdentifier": "<profile-id>"
  }
}

Not the flat "profileId" field that was previously used.

* feat: support AWS credential provider chain via AWS CLI

- Try env vars first (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
- Fall back to 'aws configure export-credentials --format env'
- Honor AWS_PROFILE environment variable
- Works with SSO, IAM roles, credential files, etc.

---------

Co-authored-by: Chris Tate <chris@ctate.dev>
This commit is contained in:
Pahud Hsieh
2026-04-02 18:33:37 -05:00
committed by GitHub
co-authored by Chris Tate
parent 89595836c6
commit 8561a755ef
7 changed files with 673 additions and 16 deletions
+25 -9
View File
@@ -6,6 +6,7 @@ use std::sync::Arc;
use futures_util::{SinkExt, StreamExt};
use serde_json::Value;
use tokio::sync::{broadcast, oneshot, Mutex};
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
use tokio_tungstenite::tungstenite::Message;
@@ -46,9 +47,29 @@ pub struct CdpClient {
impl CdpClient {
pub async fn connect(url: &str) -> Result<Self, String> {
// Use unlimited message/frame sizes to handle large CDP responses
// (e.g. Accessibility.getFullAXTree) over remote WSS connections where
// proxies may produce frames exceeding the default 16 MiB limit.
Self::connect_with_headers(url, None).await
}
pub async fn connect_with_headers(
url: &str,
headers: Option<Vec<(String, String)>>,
) -> Result<Self, String> {
let mut request = url
.into_client_request()
.map_err(|e| format!("Invalid WebSocket URL: {}", e))?;
if let Some(hdrs) = headers {
let req_headers = request.headers_mut();
for (key, value) in hdrs {
if let (Ok(name), Ok(val)) = (
key.parse::<tokio_tungstenite::tungstenite::http::header::HeaderName>(),
value.parse::<tokio_tungstenite::tungstenite::http::header::HeaderValue>(),
) {
req_headers.insert(name, val);
}
}
}
let ws_config = WebSocketConfig {
max_message_size: None,
max_frame_size: None,
@@ -56,15 +77,10 @@ impl CdpClient {
};
let (ws_stream, _) =
tokio_tungstenite::connect_async_with_config(url, Some(ws_config), false)
tokio_tungstenite::connect_async_with_config(request, Some(ws_config), false)
.await
.map_err(|e| format!("CDP WebSocket connect failed: {}", e))?;
// Enable TCP SO_KEEPALIVE on the underlying socket. This matches the
// behavior of Playwright's WebSocket transport (pre-v0.20.0) which used
// Node.js HTTP agents with keepAlive: true. TCP-level keepalive probes
// maintain the connection at the transport layer, complementing the
// WebSocket-level Ping frames sent by the keepalive task below.
enable_tcp_keepalive(ws_stream.get_ref());
let (ws_tx, mut ws_rx) = ws_stream.split();