| Crates.io | stagehand_sdk |
| lib.rs | stagehand_sdk |
| version | 0.3.2 |
| created_at | 2025-12-15 23:44:03.20903+00 |
| updated_at | 2025-12-15 23:59:53.132333+00 |
| description | Rust SDK for Stagehand - AI-powered browser automation with Browserbase |
| homepage | |
| repository | https://github.com/browserbase/stagehand-rust |
| max_upload_size | |
| id | 1986963 |
| size | 148,534 |
A Rust client library for Stagehand, the AI-powered browser automation framework. This SDK provides an async-first, type-safe interface for controlling browsers and performing AI-driven web interactions.
[!CAUTION] This is an ALPHA release and is not production-ready. Please provide feedback and let us know if you have feature requests / bug reports!
execute methodAdd to your Cargo.toml:
[dependencies]
stagehand_sdk = "0.3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures = "0.3"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
dotenvy = "0.15"
The SDK supports both tokio and async-std runtimes. Tokio is enabled by default.
Using tokio (default):
[dependencies]
stagehand_sdk = "0.3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Using async-std:
[dependencies]
stagehand_sdk = { version = "0.3", default-features = false, features = ["async-std-runtime"] }
async-std = { version = "1", features = ["attributes"] }
use stagehand_sdk::{Stagehand, V3Options, Env, Model, TransportChoice};
use stagehand_sdk::{ActResponseEvent, ExtractResponseEvent};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
struct Quote {
text: String,
author: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Load environment variables from .env file
dotenvy::dotenv().ok();
// Environment variables required:
// - BROWSERBASE_API_KEY
// - BROWSERBASE_PROJECT_ID
// - A model API key (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, etc.)
// 1. Connect to Stagehand cloud API
let mut stagehand = Stagehand::connect(
TransportChoice::Rest("https://api.stagehand.browserbase.com/v1".to_string())
).await?;
// 2. Start session
let opts = V3Options {
env: Some(Env::Browserbase),
model: Some(Model::String("openai/gpt-5-nano".into())),
verbose: Some(2),
..Default::default()
};
stagehand.start(opts).await?;
println!("Session ID: {:?}", stagehand.session_id());
// 3. Navigate to a page
let mut act_stream = stagehand.act(
"Go to https://quotes.toscrape.com/",
None,
HashMap::new(),
Some(60_000),
None,
).await?;
while let Some(res) = act_stream.next().await {
if let Ok(response) = res {
if let Some(ActResponseEvent::Success(success)) = response.event {
println!("Navigation success: {}", success);
}
}
}
// 4. Extract structured data
let schema = serde_json::json!({
"type": "object",
"properties": {
"text": { "type": "string" },
"author": { "type": "string" }
}
});
let mut extract_stream = stagehand.extract(
"Extract the first quote on the page",
schema,
None,
Some(60_000),
None,
None,
).await?;
while let Some(res) = extract_stream.next().await {
if let Ok(response) = res {
if let Some(ExtractResponseEvent::DataJson(json)) = response.event {
println!("Quote: {}", json);
}
}
}
// 5. End session
stagehand.end().await?;
Ok(())
}
Create a .env file in your project root:
# Browserbase API credentials (required)
BROWSERBASE_API_KEY=your_browserbase_api_key_here
BROWSERBASE_PROJECT_ID=your_browserbase_project_id_here
# Model API key (at least one required, checked in this order)
MODEL_API_KEY=your_api_key # Generic override (highest priority)
OPENAI_API_KEY=your_openai_key # OpenAI
ANTHROPIC_API_KEY=your_anthropic_key # Anthropic (Claude)
GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_key # Google Gemini
AZURE_API_KEY=your_azure_key # Azure OpenAI
MISTRAL_API_KEY=your_mistral_key # Mistral
GROQ_API_KEY=your_groq_key # Groq
CEREBRAS_API_KEY=your_cerebras_key # Cerebras
DEEPSEEK_API_KEY=your_deepseek_key # DeepSeek
The SDK checks for model API keys in the order listed above and uses the first one found.
The main configuration struct for initializing Stagehand:
pub struct V3Options {
// Environment: Local or Browserbase
pub env: Option<Env>,
// Browserbase credentials (auto-loaded from env vars)
pub api_key: Option<String>,
pub project_id: Option<String>,
pub browserbase_session_id: Option<String>,
pub browserbase_session_create_params: Option<serde_json::Value>,
// Local browser options (coming soon)
// pub local_browser_launch_options: Option<LocalBrowserLaunchOptions>,
// AI model configuration
pub model: Option<Model>,
pub system_prompt: Option<String>,
// Behavior settings
pub self_heal: Option<bool>,
pub wait_for_captcha_solves: Option<bool>,
pub experimental: Option<bool>,
pub dom_settle_timeout_ms: Option<u32>,
pub act_timeout_ms: Option<u32>,
// Logging verbosity (0, 1, or 2)
pub verbose: Option<i32>,
}
Specify AI models in two ways:
// Simple string format (recommended)
let model = Model::String("openai/gpt-5-nano".into());
// Detailed configuration with custom API key/base URL
let model = Model::Config {
model_name: "gpt-5-nano".to_string(),
api_key: Some("sk-...".to_string()),
base_url: Some("https://api.openai.com/v1".to_string()),
};
Stagehand::connectEstablishes a connection to the Stagehand service.
pub async fn connect(
transport_choice: TransportChoice,
) -> Result<Self, StagehandError>
Parameters:
transport_choice - TransportChoice::Rest(base_url) for REST APIExample:
let stagehand = Stagehand::connect(
TransportChoice::Rest("https://api.stagehand.browserbase.com/v1".to_string()),
).await?;
startStarts a browser session.
pub async fn start(&mut self, opts: V3Options) -> Result<(), StagehandError>
Example:
let opts = V3Options {
env: Some(Env::Browserbase),
model: Some(Model::String("openai/gpt-5-nano".into())),
verbose: Some(1),
..Default::default()
};
stagehand.start(opts).await?;
println!("Session: {}", stagehand.session_id().unwrap());
actPerforms browser actions based on natural language instructions.
pub async fn act(
&mut self,
instruction: impl Into<String>,
model: Option<Model>,
variables: HashMap<String, String>,
timeout: Option<u32>,
frame_id: Option<String>,
) -> Result<Pin<Box<dyn Stream<Item = Result<ActResponse, StagehandError>> + Send>>, StagehandError>
Parameters:
instruction - Natural language instruction (e.g., "Click the login button")model - Override the default AI modelvariables - Variable substitution map for the instructiontimeout - Operation timeout in millisecondsframe_id - Target a specific iframeResponse Events:
ActResponseEvent::Log(LogLine) - Progress logsActResponseEvent::Success(bool) - Action completion statusExample:
let mut stream = stagehand.act(
"Navigate to https://example.com and click 'More information...'",
None,
HashMap::new(),
Some(60_000),
None,
).await?;
while let Some(res) = stream.next().await {
if let Ok(response) = res {
if let Some(ActResponseEvent::Success(success)) = response.event {
println!("Action succeeded: {}", success);
}
}
}
extractExtracts structured data from web pages using a schema.
pub async fn extract<S: Serialize>(
&mut self,
instruction: impl Into<String>,
schema: &S,
model: Option<Model>,
timeout: Option<u32>,
selector: Option<String>,
frame_id: Option<String>,
) -> Result<Pin<Box<dyn Stream<Item = Result<ExtractResponse, StagehandError>> + Send>>, StagehandError>
Parameters:
instruction - What data to extractschema - A Serde-serializable struct defining the expected shapemodel - Override the default AI modeltimeout - Operation timeoutselector - CSS selector to narrow extraction scopeframe_id - Target a specific iframeResponse Events:
ExtractResponseEvent::Log(LogLine) - Progress logsExtractResponseEvent::DataJson(String) - JSON string matching the schemaExample:
#[derive(Serialize, Deserialize, Debug)]
struct ProductInfo {
name: String,
price: String,
description: String,
}
let schema = ProductInfo {
name: String::new(),
price: String::new(),
description: String::new(),
};
let mut stream = stagehand.extract(
"Extract the product information from this page",
&schema,
None,
Some(30_000),
None,
None,
).await?;
while let Some(res) = stream.next().await {
if let Ok(response) = res {
if let Some(ExtractResponseEvent::DataJson(json)) = response.event {
let product: ProductInfo = serde_json::from_str(&json)?;
println!("Product: {:?}", product);
}
}
}
observeIdentifies interactive elements on a page.
pub async fn observe(
&mut self,
instruction: Option<String>,
model: Option<Model>,
timeout: Option<u32>,
selector: Option<String>,
frame_id: Option<String>,
) -> Result<Pin<Box<dyn Stream<Item = Result<ObserveResponse, StagehandError>> + Send>>, StagehandError>
Parameters:
instruction - Optional AI instruction for analysismodel - Override the default AI modeltimeout - Operation timeoutselector - CSS selector to narrow observation scopeframe_id - Target a specific iframeResponse Events:
ObserveResponseEvent::Log(LogLine) - Progress logsObserveResponseEvent::ElementsJson(String) - JSON array of observed elementsExample:
let mut stream = stagehand.observe(
Some("Find all clickable buttons".to_string()),
None,
Some(30_000),
None,
None,
).await?;
while let Some(res) = stream.next().await {
if let Ok(response) = res {
if let Some(ObserveResponseEvent::ElementsJson(json)) = response.event {
println!("Elements: {}", json);
}
}
}
executeExecutes an AI agent with multi-step capabilities.
pub async fn execute(
&mut self,
agent_config: AgentConfig,
execute_options: AgentExecuteOptions,
frame_id: Option<String>,
) -> Result<Pin<Box<dyn Stream<Item = Result<ExecuteResponse, StagehandError>> + Send>>, StagehandError>
Parameters:
agent_config - Agent configuration (provider, model, system prompt, CUA mode)execute_options - Execution options (instruction, max steps, highlight cursor)frame_id - Target a specific iframeResponse Events:
ExecuteResponseEvent::Log(LogLine) - Execution progressExecuteResponseEvent::ResultJson(String) - Final resultExample:
use stagehand_sdk::{AgentConfig, AgentExecuteOptions, ModelConfiguration};
let agent_config = AgentConfig {
provider: None,
model: Some(ModelConfiguration::String("openai/gpt-5-nano".into())),
system_prompt: None,
cua: None,
};
let execute_options = AgentExecuteOptions {
instruction: "What is the URL of the current page?".to_string(),
max_steps: Some(10),
highlight_cursor: None,
};
let mut stream = stagehand.execute(
agent_config,
execute_options,
None,
).await?;
while let Some(res) = stream.next().await {
if let Ok(response) = res {
if let Some(ExecuteResponseEvent::ResultJson(result)) = response.event {
println!("Result: {}", result);
}
}
}
endEnds the browser session.
pub async fn end(&mut self) -> Result<(), StagehandError>
Example:
stagehand.end().await?;
browserbase_cdp_urlReturns the CDP WebSocket URL for connecting external tools like chromiumoxide.
pub fn browserbase_cdp_url(&self) -> Option<String>
The URL format is: wss://connect.browserbase.com?sessionId={sessionId}&apiKey={apiKey}
Example:
// After init(), get the CDP URL to connect chromiumoxide
let cdp_url = stagehand.browserbase_cdp_url()
.expect("CDP URL available after init");
// Connect chromiumoxide to the remote browser
let (browser, handler) = Browser::connect(&cdp_url).await?;
See tests/chromiumoxide_integration.rs for a complete example.
See tests/browserbase_live.rs for a complete working example that demonstrates act, extract, and execute.
See tests/chromiumoxide_integration.rs for connecting chromiumoxide to a Browserbase session:
use chromiumoxide::browser::Browser;
use stagehand_sdk::{Stagehand, V3Options, Env, Model, TransportChoice};
async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 1. Create Stagehand session
let mut stagehand = Stagehand::connect(
TransportChoice::Rest("https://api.stagehand.browserbase.com/v1".to_string())
).await?;
stagehand.start(V3Options {
env: Some(Env::Browserbase),
model: Some(Model::String("openai/gpt-5-nano".into())),
..Default::default()
}).await?;
// 2. Get CDP URL and connect chromiumoxide
let cdp_url = stagehand.browserbase_cdp_url().unwrap();
let (browser, mut handler) = Browser::connect(&cdp_url).await?;
// Spawn handler
tokio::spawn(async move {
while let Some(event) = handler.next().await {
if event.is_err() { break; }
}
});
// 3. Use chromiumoxide for direct CDP control
let page = browser.pages().await?.into_iter().next().unwrap();
let screenshot = page.screenshot(Default::default()).await?;
// 4. Or use Stagehand's AI methods
let mut stream = stagehand.act("Click the login button", None, Default::default(), None, None).await?;
// ...
stagehand.end().await?;
Ok(())
}
The SDK uses StagehandError for all error cases:
pub enum StagehandError {
Transport(String), // Network/connection errors
Api(String), // API response errors
MissingApiKey(String), // Missing required environment variable
}
All errors implement std::error::Error and Display.
# Set up environment variables
cp .env.example .env
# Edit .env with your credentials
# Run all tests
cargo test
# Run specific integration test with output
cargo test test_browserbase_live_extract -- --nocapture
# Run chromiumoxide integration test
cargo test test_chromiumoxide_browserbase_connection -- --nocapture
Apache-2.0