@@ -626,7 +626,7 @@ The dashboard displays:
|
|||||||
- **Live viewport** -- real-time JPEG frames from the browser
|
- **Live viewport** -- real-time JPEG frames from the browser
|
||||||
- **Activity feed** -- chronological command/result stream with timing and expandable details
|
- **Activity feed** -- chronological command/result stream with timing and expandable details
|
||||||
- **Console output** -- browser console messages (log, warn, error)
|
- **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
|
## 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).
|
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
|
## License
|
||||||
|
|
||||||
Apache-2.0
|
Apache-2.0
|
||||||
|
|||||||
+4
-10
@@ -10,10 +10,6 @@ readme = "../README.md"
|
|||||||
keywords = ["browser", "automation", "ai", "cdp", "chrome"]
|
keywords = ["browser", "automation", "ai", "cdp", "chrome"]
|
||||||
categories = ["command-line-utilities", "web-programming"]
|
categories = ["command-line-utilities", "web-programming"]
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
agentcore = ["hmac", "hex", "chrono", "urlencoding"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -34,12 +30,10 @@ socket2 = "0.6"
|
|||||||
similar = "2"
|
similar = "2"
|
||||||
zip = { version = "8.2.0", default-features = false, features = ["deflate"] }
|
zip = { version = "8.2.0", default-features = false, features = ["deflate"] }
|
||||||
time = { version = "0.3", features = ["formatting"] }
|
time = { version = "0.3", features = ["formatting"] }
|
||||||
|
hmac = "0.12"
|
||||||
# AgentCore provider (optional - lightweight SigV4 signing)
|
hex = "0.4"
|
||||||
hmac = { version = "0.12", optional = true }
|
chrono = "0.4"
|
||||||
hex = { version = "0.4", optional = true }
|
urlencoding = "2"
|
||||||
chrono = { version = "0.4", optional = true }
|
|
||||||
urlencoding = { version = "2", optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|||||||
@@ -1629,8 +1629,7 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
|
|||||||
let connect_result = if conn.direct_page {
|
let connect_result = if conn.direct_page {
|
||||||
BrowserManager::connect_cdp_direct(&conn.ws_url).await
|
BrowserManager::connect_cdp_direct(&conn.ws_url).await
|
||||||
} else if ws_headers.is_some() {
|
} else if ws_headers.is_some() {
|
||||||
BrowserManager::connect_cdp_with_headers(&conn.ws_url, ws_headers)
|
BrowserManager::connect_cdp_with_headers(&conn.ws_url, ws_headers).await
|
||||||
.await
|
|
||||||
} else {
|
} else {
|
||||||
BrowserManager::connect_cdp(&conn.ws_url).await
|
BrowserManager::connect_cdp(&conn.ws_url).await
|
||||||
};
|
};
|
||||||
@@ -1644,7 +1643,6 @@ async fn handle_launch(cmd: &Value, state: &mut DaemonState) -> Result<Value, St
|
|||||||
state.update_stream_client().await;
|
state.update_stream_client().await;
|
||||||
write_provider_file(&state.session_id, provider);
|
write_provider_file(&state.session_id, provider);
|
||||||
|
|
||||||
#[cfg(feature = "agentcore")]
|
|
||||||
if let Some(info) = providers::get_agentcore_info() {
|
if let Some(info) = providers::get_agentcore_info() {
|
||||||
return Ok(json!({
|
return Ok(json!({
|
||||||
"launched": true,
|
"launched": true,
|
||||||
|
|||||||
+65
-66
@@ -1,6 +1,6 @@
|
|||||||
//! Browser provider connections for remote CDP sessions.
|
//! Browser provider connections for remote CDP sessions.
|
||||||
//!
|
//!
|
||||||
//! Supports Browserbase, Browserless, Browser Use, and Kernel providers.
|
//! Supports AgentCore, Browserbase, Browserless, Browser Use, and Kernel providers.
|
||||||
//! Each provider returns a CDP WebSocket URL for connecting via BrowserManager.
|
//! Each provider returns a CDP WebSocket URL for connecting via BrowserManager.
|
||||||
|
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
@@ -364,10 +364,8 @@ async fn connect_kernel() -> Result<(String, Option<ProviderSession>), String> {
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// AgentCore Provider (AWS Bedrock AgentCore Browser)
|
// AgentCore Provider (AWS Bedrock AgentCore Browser)
|
||||||
// Requires: cargo build --features agentcore
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#[cfg(feature = "agentcore")]
|
|
||||||
mod agentcore {
|
mod agentcore {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -389,12 +387,14 @@ mod agentcore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_agentcore_info() -> Option<AgentCoreSessionInfo> {
|
pub fn get_agentcore_info() -> Option<AgentCoreSessionInfo> {
|
||||||
AGENTCORE_INFO.with(|cell| cell.borrow().as_ref().map(|i| AgentCoreSessionInfo {
|
AGENTCORE_INFO.with(|cell| {
|
||||||
|
cell.borrow().as_ref().map(|i| AgentCoreSessionInfo {
|
||||||
session_id: i.session_id.clone(),
|
session_id: i.session_id.clone(),
|
||||||
browser_identifier: i.browser_identifier.clone(),
|
browser_identifier: i.browser_identifier.clone(),
|
||||||
region: i.region.clone(),
|
region: i.region.clone(),
|
||||||
live_view_url: i.live_view_url.clone(),
|
live_view_url: i.live_view_url.clone(),
|
||||||
}))
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_agentcore_ws_headers(headers: Vec<(String, String)>) {
|
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_REGION"))
|
||||||
.or_else(|_| env::var("AWS_DEFAULT_REGION"))
|
.or_else(|_| env::var("AWS_DEFAULT_REGION"))
|
||||||
.unwrap_or_else(|_| "us-east-1".to_string());
|
.unwrap_or_else(|_| "us-east-1".to_string());
|
||||||
let browser_id = env::var("AGENTCORE_BROWSER_ID")
|
let browser_id =
|
||||||
.unwrap_or_else(|_| "aws.browser.v1".to_string());
|
env::var("AGENTCORE_BROWSER_ID").unwrap_or_else(|_| "aws.browser.v1".to_string());
|
||||||
let timeout_secs: u64 = env::var("AGENTCORE_SESSION_TIMEOUT")
|
let timeout_secs: u64 = env::var("AGENTCORE_SESSION_TIMEOUT")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(3600);
|
.unwrap_or(3600);
|
||||||
|
|
||||||
let host = format!("bedrock-agentcore.{}.amazonaws.com", region);
|
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);
|
let url = format!("https://{}{}", host, path);
|
||||||
|
|
||||||
// Generate a unique session name
|
// Generate a unique session name
|
||||||
@@ -432,7 +435,7 @@ mod agentcore {
|
|||||||
if !profile_id.is_empty() {
|
if !profile_id.is_empty() {
|
||||||
body_json.as_object_mut().unwrap().insert(
|
body_json.as_object_mut().unwrap().insert(
|
||||||
"profileConfiguration".to_string(),
|
"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());
|
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))?;
|
.map_err(|e| format!("AgentCore request failed: {}", e))?;
|
||||||
|
|
||||||
let status = response.status();
|
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))?;
|
.map_err(|e| format!("Failed to read AgentCore response: {}", e))?;
|
||||||
|
|
||||||
if !status.is_success() {
|
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)
|
let json: Value = serde_json::from_str(&resp_body)
|
||||||
.map_err(|e| format!("Invalid AgentCore response: {}", e))?;
|
.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())
|
.and_then(|v| v.as_str())
|
||||||
.ok_or_else(|| "AgentCore response missing sessionId".to_string())?
|
.ok_or_else(|| "AgentCore response missing sessionId".to_string())?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let browser_identifier = json.get("browserIdentifier")
|
let browser_identifier = json
|
||||||
|
.get("browserIdentifier")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or(&browser_id)
|
.unwrap_or(&browser_id)
|
||||||
.to_string();
|
.to_string();
|
||||||
@@ -486,10 +499,19 @@ mod agentcore {
|
|||||||
eprintln!("Session: {}", session_id);
|
eprintln!("Session: {}", session_id);
|
||||||
eprintln!("Live View: {}", live_view_url);
|
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_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);
|
set_agentcore_ws_headers(ws_headers);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@@ -525,7 +547,10 @@ mod agentcore {
|
|||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
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);
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
@@ -556,13 +581,12 @@ mod agentcore {
|
|||||||
body: Option<&str>,
|
body: Option<&str>,
|
||||||
) -> Result<Vec<(String, String)>, String> {
|
) -> Result<Vec<(String, String)>, String> {
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
// Get credentials from environment or AWS CLI
|
// Get credentials from environment or AWS CLI
|
||||||
let (access_key, secret_key, session_token) = get_aws_credentials()?;
|
let (access_key, secret_key, session_token) = get_aws_credentials()?;
|
||||||
|
|
||||||
let parsed_url = url::Url::parse(url)
|
let parsed_url = url::Url::parse(url).map_err(|e| format!("Invalid URL: {}", e))?;
|
||||||
.map_err(|e| format!("Invalid URL: {}", e))?;
|
|
||||||
let host = parsed_url.host_str().unwrap_or("");
|
let host = parsed_url.host_str().unwrap_or("");
|
||||||
|
|
||||||
// Get current time
|
// Get current time
|
||||||
@@ -576,7 +600,8 @@ mod agentcore {
|
|||||||
hasher.update(b.as_bytes());
|
hasher.update(b.as_bytes());
|
||||||
hex::encode(hasher.finalize())
|
hex::encode(hasher.finalize())
|
||||||
} else {
|
} else {
|
||||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string() // empty string hash
|
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string()
|
||||||
|
// empty string hash
|
||||||
};
|
};
|
||||||
|
|
||||||
let canonical_uri = parsed_url.path();
|
let canonical_uri = parsed_url.path();
|
||||||
@@ -588,18 +613,22 @@ mod agentcore {
|
|||||||
host, amz_date
|
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();
|
signed_headers = "content-type;host;x-amz-date;x-amz-security-token".to_string();
|
||||||
canonical_headers = format!(
|
canonical_headers = format!(
|
||||||
"content-type:application/json\nhost:{}\nx-amz-date:{}\nx-amz-security-token:{}\n",
|
"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!(
|
let canonical_request = format!(
|
||||||
"{}\n{}\n{}\n{}\n{}\n{}",
|
"{}\n{}\n{}\n{}\n{}\n{}",
|
||||||
method, canonical_uri, canonical_querystring,
|
method,
|
||||||
canonical_headers, signed_headers, payload_hash
|
canonical_uri,
|
||||||
|
canonical_querystring,
|
||||||
|
canonical_headers,
|
||||||
|
signed_headers,
|
||||||
|
payload_hash
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create string to sign
|
// Create string to sign
|
||||||
@@ -647,7 +676,7 @@ mod agentcore {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.chain_update(string_to_sign.as_bytes())
|
.chain_update(string_to_sign.as_bytes())
|
||||||
.finalize()
|
.finalize()
|
||||||
.into_bytes()
|
.into_bytes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build authorization header
|
// Build authorization header
|
||||||
@@ -686,7 +715,10 @@ mod agentcore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let host = format!("bedrock-agentcore.{}.amazonaws.com", region);
|
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 url = format!("https://{}{}", host, path);
|
||||||
|
|
||||||
let body = serde_json::to_string(&json!({ "sessionId": session_id }))
|
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};
|
pub use agentcore::{get_agentcore_info, take_agentcore_ws_headers};
|
||||||
|
|
||||||
#[cfg(feature = "agentcore")]
|
|
||||||
async fn connect_agentcore() -> Result<(String, Option<ProviderSession>), String> {
|
async fn connect_agentcore() -> Result<(String, Option<ProviderSession>), String> {
|
||||||
agentcore::connect().await
|
agentcore::connect().await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "agentcore"))]
|
|
||||||
async fn connect_agentcore() -> Result<(String, Option<ProviderSession>), 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> {
|
async fn close_agentcore_session(session_id: &str) -> Result<(), String> {
|
||||||
agentcore::close_session(session_id).await
|
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<Vec<(String, String)>> { None }
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -748,19 +759,6 @@ mod tests {
|
|||||||
assert!(result.unwrap_err().contains("Unknown provider"));
|
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]
|
#[test]
|
||||||
fn test_agentcore_env_defaults() {
|
fn test_agentcore_env_defaults() {
|
||||||
// Test that default values are used when env vars not set
|
// 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());
|
.unwrap_or_else(|_| "us-east-1".to_string());
|
||||||
assert_eq!(region, "us-east-1");
|
assert_eq!(region, "us-east-1");
|
||||||
|
|
||||||
let browser_id = std::env::var("AGENTCORE_BROWSER_ID")
|
let browser_id =
|
||||||
.unwrap_or_else(|_| "aws.browser.v1".to_string());
|
std::env::var("AGENTCORE_BROWSER_ID").unwrap_or_else(|_| "aws.browser.v1".to_string());
|
||||||
assert_eq!(browser_id, "aws.browser.v1");
|
assert_eq!(browser_id, "aws.browser.v1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "agentcore")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_agentcore_session_info_storage() {
|
fn test_agentcore_session_info_storage() {
|
||||||
let info = agentcore::AgentCoreSessionInfo {
|
let info = agentcore::AgentCoreSessionInfo {
|
||||||
@@ -797,11 +794,13 @@ mod tests {
|
|||||||
assert_eq!(retrieved.region, "us-east-1");
|
assert_eq!(retrieved.region, "us-east-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "agentcore")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_agentcore_ws_headers_storage() {
|
fn test_agentcore_ws_headers_storage() {
|
||||||
let headers = vec![
|
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()),
|
("X-Amz-Date".to_string(), "20260304T180000Z".to_string()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ The dashboard is a single-page web app with three areas:
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Session creation</strong></td>
|
<td><strong>Session creation</strong></td>
|
||||||
<td>Create new sessions from the dashboard with local engines (Chrome, Lightpanda) or cloud providers (Browserbase, Browserless, Browser Use, Kernel)</td>
|
<td>Create new sessions from the dashboard with local engines (Chrome, Lightpanda) or cloud providers (AgentCore, Browserbase, Browserless, Browser Use, Kernel)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Status bar</strong></td>
|
<td><strong>Status bar</strong></td>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr><th>Variable</th><th>Description</th><th>Default</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>AGENTCORE_REGION</code></td><td>AWS region for the AgentCore endpoint</td><td><code>us-east-1</code></td></tr>
|
||||||
|
<tr><td><code>AGENTCORE_BROWSER_ID</code></td><td>Browser identifier</td><td><code>aws.browser.v1</code></td></tr>
|
||||||
|
<tr><td><code>AGENTCORE_PROFILE_ID</code></td><td>Browser profile for persistent state (cookies, localStorage)</td><td>(none)</td></tr>
|
||||||
|
<tr><td><code>AGENTCORE_SESSION_TIMEOUT</code></td><td>Session timeout in seconds</td><td><code>3600</code></td></tr>
|
||||||
|
<tr><td><code>AWS_PROFILE</code></td><td>AWS CLI profile for credential resolution</td><td><code>default</code></td></tr>
|
||||||
|
<tr><td><code>AWS_ACCESS_KEY_ID</code></td><td>AWS access key (checked before AWS CLI fallback)</td><td>(none)</td></tr>
|
||||||
|
<tr><td><code>AWS_SECRET_ACCESS_KEY</code></td><td>AWS secret key</td><td>(none)</td></tr>
|
||||||
|
<tr><td><code>AWS_SESSION_TOKEN</code></td><td>Temporary session token (for STS/SSO credentials)</td><td>(none)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -45,6 +45,7 @@ export const navigation: NavSection[] = [
|
|||||||
{
|
{
|
||||||
title: "Providers",
|
title: "Providers",
|
||||||
items: [
|
items: [
|
||||||
|
{ name: "AgentCore", href: "/providers/agentcore" },
|
||||||
{ name: "Browser Use", href: "/providers/browser-use" },
|
{ name: "Browser Use", href: "/providers/browser-use" },
|
||||||
{ name: "Browserbase", href: "/providers/browserbase" },
|
{ name: "Browserbase", href: "/providers/browserbase" },
|
||||||
{ name: "Browserless", href: "/providers/browserless" },
|
{ name: "Browserless", href: "/providers/browserless" },
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const PAGE_TITLES: Record<string, string> = {
|
|||||||
"engines/lightpanda": "Lightpanda",
|
"engines/lightpanda": "Lightpanda",
|
||||||
next: "Next.js + Vercel",
|
next: "Next.js + Vercel",
|
||||||
"native-mode": "Native Mode",
|
"native-mode": "Native Mode",
|
||||||
|
"providers/agentcore": "AgentCore",
|
||||||
"providers/browser-use": "Browser Use",
|
"providers/browser-use": "Browser Use",
|
||||||
"providers/browserbase": "Browserbase",
|
"providers/browserbase": "Browserbase",
|
||||||
"providers/browserless": "Browserless",
|
"providers/browserless": "Browserless",
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" version="1.1" viewBox="0 0 200 200" width="200" height="200" >
|
||||||
|
<!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
|
||||||
|
<defs>
|
||||||
|
<!-- <style>
|
||||||
|
.st0 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1 {
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st1, .st2 {
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st3 {
|
||||||
|
fill: url(#linear-gradient1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st4 {
|
||||||
|
fill: url(#linear-gradient);
|
||||||
|
}
|
||||||
|
|
||||||
|
.st5 {
|
||||||
|
clip-path: url(#clippath);
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
|
<linearGradient id="linear-gradient" x1="-332.91" y1="-2554.29" x2="-598.06" y2="-2502.11" gradientTransform="translate(564 2628.67)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".38" stop-color="#7638fa"/>
|
||||||
|
<stop offset="1" stop-color="#341478" stop-opacity=".68"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="linear-gradient1" x1="-332.91" y1="-2554.29" x2="-598.06" y2="-2502.11" gradientTransform="translate(564 2628.67)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset=".38" stop-color="#7638fa"/>
|
||||||
|
<stop offset="1" stop-color="#341478" stop-opacity=".68"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clippath">
|
||||||
|
<rect fill="none" x="102.77" y="37.56" width="93.05" height="122.85"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<rect fill="url(#linear-gradient)" y=".18" width="200" height="200"/>
|
||||||
|
<rect fill="url(#linear-gradient1)" y=".18" width="200" height="200"/>
|
||||||
|
<polygon fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" points="102.77 37.56 102.77 163.02 78.76 171.14 46.56 152.18 46.56 126.84 27.9 115.86 27.9 84.72 46.33 73.81 46.33 48.4 78.84 29.72 102.77 37.56"/>
|
||||||
|
<line fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" x1="65.33" y1="63.71" x2="65.33" y2="38.73"/>
|
||||||
|
<line fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" x1="62.74" y1="139.69" x2="103.32" y2="115.79"/>
|
||||||
|
<line fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" x1="86.53" y1="149.78" x2="65.71" y2="162.9"/>
|
||||||
|
<line fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" x1="63.28" y1="115.79" x2="45.93" y2="126.73"/>
|
||||||
|
<polyline fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" points="81.06 55.86 81.06 73.81 65.33 84.88 46.33 73.81"/>
|
||||||
|
<polyline fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" points="51.94 105.49 65.33 97.45 65.33 85.58"/>
|
||||||
|
<line fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" x1="78.72" y1="105.49" x2="65.33" y2="97.45"/>
|
||||||
|
<g clip-path="url(#clippath)">
|
||||||
|
<g>
|
||||||
|
<path fill="none" stroke="#fff" stroke-linejoin="round" stroke-width="6px" d="M139.93,88.57l25.05,10.04c1.49.6,1.48,2.71-.02,3.29l-24.51,9.47-10.2,25.29c-.61,1.5-2.74,1.47-3.3-.05l-9.22-24.93-25.52-9.76c-1.51-.58-1.52-2.72,0-3.3l24.84-9.63,9.86-25.14c.59-1.5,2.7-1.5,3.29,0l9.75,24.73h-.02Z"/>
|
||||||
|
<path fill="#fff" d="M165.25,66.92l9.5,3.81c.41.16.4.74,0,.9l-9.3,3.59-3.87,9.59c-.17.41-.75.4-.9-.01l-3.5-9.46-9.68-3.7c-.41-.16-.41-.74,0-.9l9.43-3.66,3.74-9.53c.16-.41.74-.41.9,0l3.7,9.38h-.02Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -53,6 +53,7 @@ const ENGINE_LOGOS: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PROVIDER_LOGOS: Record<string, string> = {
|
const PROVIDER_LOGOS: Record<string, string> = {
|
||||||
|
agentcore: "/providers/agentcore.svg",
|
||||||
browserbase: "/providers/browserbase.svg",
|
browserbase: "/providers/browserbase.svg",
|
||||||
browserless: "/providers/browserless.svg",
|
browserless: "/providers/browserless.svg",
|
||||||
"browser-use": "/providers/browser-use.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 }[] = [
|
const BROWSER_OPTIONS: { id: string; label: string; engine?: string; provider?: string }[] = [
|
||||||
{ id: "chrome", label: "Chrome", engine: "chrome" },
|
{ id: "chrome", label: "Chrome", engine: "chrome" },
|
||||||
{ id: "lightpanda", label: "Lightpanda", engine: "lightpanda" },
|
{ id: "lightpanda", label: "Lightpanda", engine: "lightpanda" },
|
||||||
|
{ id: "agentcore", label: "AgentCore", provider: "agentcore" },
|
||||||
{ id: "browserbase", label: "Browserbase", provider: "browserbase" },
|
{ id: "browserbase", label: "Browserbase", provider: "browserbase" },
|
||||||
{ id: "browserless", label: "Browserless", provider: "browserless" },
|
{ id: "browserless", label: "Browserless", provider: "browserless" },
|
||||||
{ id: "browser-use", label: "Browser Use", provider: "browser-use" },
|
{ id: "browser-use", label: "Browser Use", provider: "browser-use" },
|
||||||
|
|||||||
@@ -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/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 |
|
| [references/proxy-support.md](references/proxy-support.md) | Proxy configuration, geo-testing, rotating proxies |
|
||||||
|
|
||||||
|
## Cloud Providers
|
||||||
|
|
||||||
|
Use `-p <provider>` (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
|
## Browser Engine Selection
|
||||||
|
|
||||||
Use `--engine` to choose a local browser engine. The default is `chrome`.
|
Use `--engine` to choose a local browser engine. The default is `chrome`.
|
||||||
|
|||||||
Reference in New Issue
Block a user