diff --git a/README.md b/README.md index 8ede376..512f1a5 100644 --- a/README.md +++ b/README.md @@ -626,7 +626,7 @@ The dashboard displays: - **Live viewport** -- real-time JPEG frames from the browser - **Activity feed** -- chronological command/result stream with timing and expandable details - **Console output** -- browser console messages (log, warn, error) -- **Session creation** -- create new sessions from the UI with local engines (Chrome, Lightpanda) or cloud providers (Browserbase, Browserless, Browser Use, Kernel) +- **Session creation** -- create new sessions from the UI with local engines (Chrome, Lightpanda) or cloud providers (AgentCore, Browserbase, Browserless, Browser Use, Kernel) ## Configuration @@ -1317,6 +1317,39 @@ When enabled, agent-browser connects to a Kernel cloud session instead of launch Get your API key from the [Kernel Dashboard](https://dashboard.onkernel.com). +### AgentCore + +[AWS Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) provides cloud browser sessions with SigV4 authentication. + +To enable AgentCore, use the `-p` flag: + +```bash +agent-browser -p agentcore open https://example.com +``` + +Or use environment variables for CI/scripts: + +```bash +export AGENT_BROWSER_PROVIDER=agentcore +agent-browser open https://example.com +``` + +Credentials are automatically resolved from environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) or the AWS CLI (`aws configure export-credentials`), which supports SSO, profiles, and IAM roles. + +Optional configuration via environment variables: + +| Variable | Description | Default | +| -------------------------- | -------------------------------------------------------------------- | ---------------- | +| `AGENTCORE_REGION` | AWS region for the AgentCore endpoint | `us-east-1` | +| `AGENTCORE_BROWSER_ID` | Browser identifier | `aws.browser.v1` | +| `AGENTCORE_PROFILE_ID` | Browser profile for persistent state (cookies, localStorage) | (none) | +| `AGENTCORE_SESSION_TIMEOUT`| Session timeout in seconds | `3600` | +| `AWS_PROFILE` | AWS CLI profile for credential resolution | `default` | + +**Browser profiles:** When `AGENTCORE_PROFILE_ID` is set, browser state (cookies, localStorage) is persisted across sessions automatically. + +When enabled, agent-browser connects to an AgentCore cloud browser session instead of launching a local browser. All commands work identically. + ## License Apache-2.0 diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 927e7fb..ce4cccb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -10,10 +10,6 @@ readme = "../README.md" keywords = ["browser", "automation", "ai", "cdp", "chrome"] categories = ["command-line-utilities", "web-programming"] -[features] -default = [] -agentcore = ["hmac", "hex", "chrono", "urlencoding"] - [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -34,12 +30,10 @@ socket2 = "0.6" similar = "2" zip = { version = "8.2.0", default-features = false, features = ["deflate"] } time = { version = "0.3", features = ["formatting"] } - -# AgentCore provider (optional - lightweight SigV4 signing) -hmac = { version = "0.12", optional = true } -hex = { version = "0.4", optional = true } -chrono = { version = "0.4", optional = true } -urlencoding = { version = "2", optional = true } +hmac = "0.12" +hex = "0.4" +chrono = "0.4" +urlencoding = "2" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/cli/src/native/actions.rs b/cli/src/native/actions.rs index 53d17b4..b2070a3 100644 --- a/cli/src/native/actions.rs +++ b/cli/src/native/actions.rs @@ -1629,8 +1629,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result Result Result<(String, Option), String> { // ============================================================================ // AgentCore Provider (AWS Bedrock AgentCore Browser) -// Requires: cargo build --features agentcore // ============================================================================ -#[cfg(feature = "agentcore")] mod agentcore { use super::*; @@ -389,12 +387,14 @@ mod agentcore { } pub fn get_agentcore_info() -> Option { - AGENTCORE_INFO.with(|cell| cell.borrow().as_ref().map(|i| AgentCoreSessionInfo { - session_id: i.session_id.clone(), - browser_identifier: i.browser_identifier.clone(), - region: i.region.clone(), - live_view_url: i.live_view_url.clone(), - })) + AGENTCORE_INFO.with(|cell| { + cell.borrow().as_ref().map(|i| AgentCoreSessionInfo { + session_id: i.session_id.clone(), + browser_identifier: i.browser_identifier.clone(), + region: i.region.clone(), + live_view_url: i.live_view_url.clone(), + }) + }) } pub fn set_agentcore_ws_headers(headers: Vec<(String, String)>) { @@ -410,15 +410,18 @@ mod agentcore { .or_else(|_| env::var("AWS_REGION")) .or_else(|_| env::var("AWS_DEFAULT_REGION")) .unwrap_or_else(|_| "us-east-1".to_string()); - let browser_id = env::var("AGENTCORE_BROWSER_ID") - .unwrap_or_else(|_| "aws.browser.v1".to_string()); + let browser_id = + env::var("AGENTCORE_BROWSER_ID").unwrap_or_else(|_| "aws.browser.v1".to_string()); let timeout_secs: u64 = env::var("AGENTCORE_SESSION_TIMEOUT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(3600); let host = format!("bedrock-agentcore.{}.amazonaws.com", region); - let path = format!("/browsers/{}/sessions/start", urlencoding::encode(&browser_id)); + let path = format!( + "/browsers/{}/sessions/start", + urlencoding::encode(&browser_id) + ); let url = format!("https://{}{}", host, path); // Generate a unique session name @@ -432,7 +435,7 @@ mod agentcore { if !profile_id.is_empty() { body_json.as_object_mut().unwrap().insert( "profileConfiguration".to_string(), - json!({ "profileIdentifier": profile_id }) + json!({ "profileIdentifier": profile_id }), ); } } @@ -447,26 +450,36 @@ mod agentcore { req = req.header(key.as_str(), value.as_str()); } - let response = req.send().await + let response = req + .send() + .await .map_err(|e| format!("AgentCore request failed: {}", e))?; let status = response.status(); - let resp_body = response.text().await + let resp_body = response + .text() + .await .map_err(|e| format!("Failed to read AgentCore response: {}", e))?; if !status.is_success() { - return Err(format!("AgentCore API error ({}): {}", status.as_u16(), resp_body)); + return Err(format!( + "AgentCore API error ({}): {}", + status.as_u16(), + resp_body + )); } let json: Value = serde_json::from_str(&resp_body) .map_err(|e| format!("Invalid AgentCore response: {}", e))?; - let session_id = json.get("sessionId") + let session_id = json + .get("sessionId") .and_then(|v| v.as_str()) .ok_or_else(|| "AgentCore response missing sessionId".to_string())? .to_string(); - let browser_identifier = json.get("browserIdentifier") + let browser_identifier = json + .get("browserIdentifier") .and_then(|v| v.as_str()) .unwrap_or(&browser_id) .to_string(); @@ -486,10 +499,19 @@ mod agentcore { eprintln!("Session: {}", session_id); eprintln!("Live View: {}", live_view_url); - let ws_path = format!("/browser-streams/{}/sessions/{}/automation", browser_identifier, session_id); + let ws_path = format!( + "/browser-streams/{}/sessions/{}/automation", + browser_identifier, session_id + ); let ws_url = format!("wss://{}{}", host, ws_path); - let ws_headers = sign_request("GET", &format!("https://{}{}", host, ws_path), ®ion, None).await?; + let ws_headers = sign_request( + "GET", + &format!("https://{}{}", host, ws_path), + ®ion, + None, + ) + .await?; set_agentcore_ws_headers(ws_headers); Ok(( @@ -514,7 +536,7 @@ mod agentcore { // Fall back to AWS CLI let mut cmd = std::process::Command::new("aws"); cmd.args(["configure", "export-credentials", "--format", "env"]); - + // Honor AWS_PROFILE if let Ok(profile) = env::var("AWS_PROFILE") { cmd.args(["--profile", &profile]); @@ -525,7 +547,10 @@ mod agentcore { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("AWS CLI failed: {}. Run 'aws sso login' or set credentials", stderr.trim())); + return Err(format!( + "AWS CLI failed: {}. Run 'aws sso login' or set credentials", + stderr.trim() + )); } let stdout = String::from_utf8_lossy(&output.stdout); @@ -556,13 +581,12 @@ mod agentcore { body: Option<&str>, ) -> Result, String> { use hmac::{Hmac, Mac}; - use sha2::{Sha256, Digest}; + use sha2::{Digest, Sha256}; // Get credentials from environment or AWS CLI let (access_key, secret_key, session_token) = get_aws_credentials()?; - let parsed_url = url::Url::parse(url) - .map_err(|e| format!("Invalid URL: {}", e))?; + let parsed_url = url::Url::parse(url).map_err(|e| format!("Invalid URL: {}", e))?; let host = parsed_url.host_str().unwrap_or(""); // Get current time @@ -576,7 +600,8 @@ mod agentcore { hasher.update(b.as_bytes()); hex::encode(hasher.finalize()) } else { - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string() // empty string hash + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string() + // empty string hash }; let canonical_uri = parsed_url.path(); @@ -588,18 +613,22 @@ mod agentcore { host, amz_date ); - if session_token.is_some() { + if let Some(ref token) = session_token { signed_headers = "content-type;host;x-amz-date;x-amz-security-token".to_string(); canonical_headers = format!( "content-type:application/json\nhost:{}\nx-amz-date:{}\nx-amz-security-token:{}\n", - host, amz_date, session_token.as_ref().unwrap() + host, amz_date, token ); } let canonical_request = format!( "{}\n{}\n{}\n{}\n{}\n{}", - method, canonical_uri, canonical_querystring, - canonical_headers, signed_headers, payload_hash + method, + canonical_uri, + canonical_querystring, + canonical_headers, + signed_headers, + payload_hash ); // Create string to sign @@ -647,7 +676,7 @@ mod agentcore { .unwrap() .chain_update(string_to_sign.as_bytes()) .finalize() - .into_bytes() + .into_bytes(), ); // Build authorization header @@ -686,7 +715,10 @@ mod agentcore { }; let host = format!("bedrock-agentcore.{}.amazonaws.com", region); - let path = format!("/browsers/{}/sessions/stop", urlencoding::encode(&browser_id)); + let path = format!( + "/browsers/{}/sessions/stop", + urlencoding::encode(&browser_id) + ); let url = format!("https://{}{}", host, path); let body = serde_json::to_string(&json!({ "sessionId": session_id })) @@ -705,37 +737,16 @@ mod agentcore { } } -#[cfg(feature = "agentcore")] pub use agentcore::{get_agentcore_info, take_agentcore_ws_headers}; -#[cfg(feature = "agentcore")] async fn connect_agentcore() -> Result<(String, Option), String> { agentcore::connect().await } -#[cfg(not(feature = "agentcore"))] -async fn connect_agentcore() -> Result<(String, Option), String> { - Err("AgentCore provider requires the 'agentcore' feature. Rebuild with: cargo build --features agentcore".to_string()) -} - -#[cfg(feature = "agentcore")] async fn close_agentcore_session(session_id: &str) -> Result<(), String> { agentcore::close_session(session_id).await } -#[cfg(not(feature = "agentcore"))] -async fn close_agentcore_session(_session_id: &str) -> Result<(), String> { - Ok(()) -} - -// Stub functions when agentcore feature is disabled -#[cfg(not(feature = "agentcore"))] -pub fn get_agentcore_info() -> Option<()> { None } - -#[cfg(not(feature = "agentcore"))] -pub fn take_agentcore_ws_headers() -> Option> { None } - - #[cfg(test)] mod tests { use super::*; @@ -748,19 +759,6 @@ mod tests { assert!(result.unwrap_err().contains("Unknown provider")); } - #[test] - fn test_connect_provider_agentcore_without_feature() { - // Without agentcore feature, should return helpful error - #[cfg(not(feature = "agentcore"))] - { - let rt = tokio::runtime::Runtime::new().unwrap(); - let result = rt.block_on(connect_provider("agentcore")); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("agentcore")); - } - } - - #[cfg(feature = "agentcore")] #[test] fn test_agentcore_env_defaults() { // Test that default values are used when env vars not set @@ -774,12 +772,11 @@ mod tests { .unwrap_or_else(|_| "us-east-1".to_string()); assert_eq!(region, "us-east-1"); - let browser_id = std::env::var("AGENTCORE_BROWSER_ID") - .unwrap_or_else(|_| "aws.browser.v1".to_string()); + let browser_id = + std::env::var("AGENTCORE_BROWSER_ID").unwrap_or_else(|_| "aws.browser.v1".to_string()); assert_eq!(browser_id, "aws.browser.v1"); } - #[cfg(feature = "agentcore")] #[test] fn test_agentcore_session_info_storage() { let info = agentcore::AgentCoreSessionInfo { @@ -797,11 +794,13 @@ mod tests { assert_eq!(retrieved.region, "us-east-1"); } - #[cfg(feature = "agentcore")] #[test] fn test_agentcore_ws_headers_storage() { let headers = vec![ - ("Authorization".to_string(), "AWS4-HMAC-SHA256...".to_string()), + ( + "Authorization".to_string(), + "AWS4-HMAC-SHA256...".to_string(), + ), ("X-Amz-Date".to_string(), "20260304T180000Z".to_string()), ]; diff --git a/docs/src/app/dashboard/page.mdx b/docs/src/app/dashboard/page.mdx index e40188f..7f60a1c 100644 --- a/docs/src/app/dashboard/page.mdx +++ b/docs/src/app/dashboard/page.mdx @@ -63,7 +63,7 @@ The dashboard is a single-page web app with three areas: Session creation - Create new sessions from the dashboard with local engines (Chrome, Lightpanda) or cloud providers (Browserbase, Browserless, Browser Use, Kernel) + Create new sessions from the dashboard with local engines (Chrome, Lightpanda) or cloud providers (AgentCore, Browserbase, Browserless, Browser Use, Kernel) Status bar diff --git a/docs/src/app/providers/agentcore/layout.tsx b/docs/src/app/providers/agentcore/layout.tsx new file mode 100644 index 0000000..8a6dc19 --- /dev/null +++ b/docs/src/app/providers/agentcore/layout.tsx @@ -0,0 +1,7 @@ +import { pageMetadata } from "@/lib/page-metadata"; + +export const metadata = pageMetadata("providers/agentcore"); + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/docs/src/app/providers/agentcore/page.mdx b/docs/src/app/providers/agentcore/page.mdx new file mode 100644 index 0000000..97c877b --- /dev/null +++ b/docs/src/app/providers/agentcore/page.mdx @@ -0,0 +1,89 @@ +# AgentCore + +[AWS Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/) provides cloud browser sessions with SigV4 authentication. Use it when running agent-browser in AWS environments or when you need managed cloud browsers backed by AWS infrastructure. + +## Setup + +Credentials are automatically resolved from: + +1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) +2. AWS CLI (`aws configure export-credentials`) which supports SSO, profiles, IAM roles, etc. + +```bash +agent-browser -p agentcore open https://example.com +``` + +Or use environment variables for CI/scripts: + +```bash +export AGENT_BROWSER_PROVIDER=agentcore +agent-browser open https://example.com +``` + +The `-p` flag takes precedence over `AGENT_BROWSER_PROVIDER`. + +## Configuration + + + + + + + + + + + + + + + +
VariableDescriptionDefault
AGENTCORE_REGIONAWS region for the AgentCore endpointus-east-1
AGENTCORE_BROWSER_IDBrowser identifieraws.browser.v1
AGENTCORE_PROFILE_IDBrowser profile for persistent state (cookies, localStorage)(none)
AGENTCORE_SESSION_TIMEOUTSession timeout in seconds3600
AWS_PROFILEAWS CLI profile for credential resolutiondefault
AWS_ACCESS_KEY_IDAWS access key (checked before AWS CLI fallback)(none)
AWS_SECRET_ACCESS_KEYAWS secret key(none)
AWS_SESSION_TOKENTemporary session token (for STS/SSO credentials)(none)
+ +## Browser Profiles + +Use `AGENTCORE_PROFILE_ID` to persist browser state (cookies, localStorage) across sessions: + +```bash +AGENTCORE_PROFILE_ID=my-profile agent-browser -p agentcore open https://example.com +``` + +When a profile is set, AgentCore stores and restores browser state automatically between sessions. + +## Live View + +When a session starts, AgentCore prints a Live View URL to stderr: + +``` +Session: abc123-def456 +Live View: https://us-east-1.console.aws.amazon.com/bedrock-agentcore/browser/aws.browser.v1/session/abc123-def456# +``` + +Open this URL in your browser to watch the agent session in real time from the AWS Console. + +## Credential Resolution + +AgentCore uses lightweight manual SigV4 signing (no AWS SDK dependency). Credentials are resolved in order: + +1. **Environment variables** (`AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`, optionally `AWS_SESSION_TOKEN`) +2. **AWS CLI** (`aws configure export-credentials --format env`), which supports SSO, IAM roles, credential files, and profiles + +If using SSO, run `aws sso login` before launching agent-browser. Set `AWS_PROFILE` to select a specific named profile. + +## Example + +```bash +# Basic usage (credentials auto-resolved via AWS CLI) +agent-browser -p agentcore open https://example.com + +# With a browser profile for persistent login state +AGENTCORE_PROFILE_ID=my-profile agent-browser -p agentcore open https://x.com/home + +# With explicit region +AGENTCORE_REGION=eu-west-1 agent-browser -p agentcore open https://example.com + +# With SSO profile +AWS_PROFILE=my-sso-profile agent-browser -p agentcore open https://example.com +``` + +When enabled, agent-browser connects to an AgentCore cloud browser session instead of launching a local browser. All commands work identically. diff --git a/docs/src/lib/docs-navigation.ts b/docs/src/lib/docs-navigation.ts index 83ce525..9ed92da 100644 --- a/docs/src/lib/docs-navigation.ts +++ b/docs/src/lib/docs-navigation.ts @@ -45,6 +45,7 @@ export const navigation: NavSection[] = [ { title: "Providers", items: [ + { name: "AgentCore", href: "/providers/agentcore" }, { name: "Browser Use", href: "/providers/browser-use" }, { name: "Browserbase", href: "/providers/browserbase" }, { name: "Browserless", href: "/providers/browserless" }, diff --git a/docs/src/lib/page-titles.ts b/docs/src/lib/page-titles.ts index 8d0bb05..15481e7 100644 --- a/docs/src/lib/page-titles.ts +++ b/docs/src/lib/page-titles.ts @@ -19,6 +19,7 @@ export const PAGE_TITLES: Record = { "engines/lightpanda": "Lightpanda", next: "Next.js + Vercel", "native-mode": "Native Mode", + "providers/agentcore": "AgentCore", "providers/browser-use": "Browser Use", "providers/browserbase": "Browserbase", "providers/browserless": "Browserless", diff --git a/packages/dashboard/public/providers/agentcore.svg b/packages/dashboard/public/providers/agentcore.svg new file mode 100644 index 0000000..88acf66 --- /dev/null +++ b/packages/dashboard/public/providers/agentcore.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/dashboard/src/components/session-tree.tsx b/packages/dashboard/src/components/session-tree.tsx index 39a8bbb..44614bd 100644 --- a/packages/dashboard/src/components/session-tree.tsx +++ b/packages/dashboard/src/components/session-tree.tsx @@ -53,6 +53,7 @@ const ENGINE_LOGOS: Record = { }; const PROVIDER_LOGOS: Record = { + agentcore: "/providers/agentcore.svg", browserbase: "/providers/browserbase.svg", browserless: "/providers/browserless.svg", "browser-use": "/providers/browser-use.svg", @@ -64,6 +65,7 @@ const SUPPORTED_ENGINES = ["chrome", "lightpanda"] as const; const BROWSER_OPTIONS: { id: string; label: string; engine?: string; provider?: string }[] = [ { id: "chrome", label: "Chrome", engine: "chrome" }, { id: "lightpanda", label: "Lightpanda", engine: "lightpanda" }, + { id: "agentcore", label: "AgentCore", provider: "agentcore" }, { id: "browserbase", label: "Browserbase", provider: "browserbase" }, { id: "browserless", label: "Browserless", provider: "browserless" }, { id: "browser-use", label: "Browser Use", provider: "browser-use" }, diff --git a/skills/agent-browser/SKILL.md b/skills/agent-browser/SKILL.md index 7ebf9ba..4a0293d 100644 --- a/skills/agent-browser/SKILL.md +++ b/skills/agent-browser/SKILL.md @@ -693,6 +693,25 @@ Priority (lowest to highest): `~/.agent-browser/config.json` < `./agent-browser. | [references/profiling.md](references/profiling.md) | Chrome DevTools profiling for performance analysis | | [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies | +## Cloud Providers + +Use `-p ` (or `AGENT_BROWSER_PROVIDER`) to run against a cloud browser instead of launching a local Chrome instance. Supported providers: `agentcore`, `browserbase`, `browserless`, `browseruse`, `kernel`. + +### AgentCore (AWS Bedrock) + +```bash +# Credentials auto-resolved from env vars or AWS CLI (SSO, IAM roles, etc.) +agent-browser -p agentcore open https://example.com + +# With persistent browser profile +AGENTCORE_PROFILE_ID=my-profile agent-browser -p agentcore open https://example.com + +# With explicit region +AGENTCORE_REGION=eu-west-1 agent-browser -p agentcore open https://example.com +``` + +Set `AWS_PROFILE` to select a named AWS profile. + ## Browser Engine Selection Use `--engine` to choose a local browser engine. The default is `chrome`.