Skip to content

Commit be7313f

Browse files
monadoidstainless-app[bot]
authored andcommitted
STG-1308: update examples to use SSE and examples env
1 parent 4e6dd49 commit be7313f

9 files changed

Lines changed: 288 additions & 112 deletions

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,18 @@ bundle install
136136
Remote browser example:
137137

138138
```bash
139-
export BROWSERBASE_API_KEY="your-browserbase-api-key"
140-
export BROWSERBASE_PROJECT_ID="your-browserbase-project-id"
141-
export MODEL_API_KEY="your-openai-api-key"
139+
cp examples/.env.example examples/.env
140+
# Edit examples/.env with your credentials.
142141
bundle exec ruby examples/remote_browser_example.rb
143142
```
144143

144+
The examples load `examples/.env` automatically.
145+
145146
Local mode example (embedded server, local Chrome/Chromium):
146147

147148
```bash
148-
export MODEL_API_KEY="your-openai-api-key"
149+
cp examples/.env.example examples/.env
150+
# Edit examples/.env with your credentials.
149151
bundle exec ruby examples/local_browser_example.rb
150152
```
151153

examples/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
STAGEHAND_API_URL=https://api.stagehand.browserbase.com
2+
MODEL_API_KEY=sk-proj-your-llm-api-key-here
3+
BROWSERBASE_API_KEY=bb_live_your_api_key_here
4+
BROWSERBASE_PROJECT_ID=your-bb-project-uuid-here

examples/env.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module ExampleEnv
4+
REQUIRED_KEYS = %w[
5+
STAGEHAND_API_URL
6+
MODEL_API_KEY
7+
BROWSERBASE_API_KEY
8+
BROWSERBASE_PROJECT_ID
9+
].freeze
10+
11+
def self.load!
12+
env_path = find_env_path
13+
raise "Missing examples/.env (expected in repo examples/ directory)." unless env_path
14+
15+
File.readlines(env_path, chomp: true).each do |line|
16+
trimmed = line.strip
17+
next if trimmed.empty? || trimmed.start_with?("#")
18+
19+
key, value = trimmed.split("=", 2)
20+
next if key.nil? || value.nil?
21+
22+
ENV[key] ||= value
23+
end
24+
25+
missing = REQUIRED_KEYS.select { |key| ENV[key].to_s.empty? }
26+
unless missing.empty?
27+
raise "Missing required env vars: #{missing.join(', ')} (from examples/.env)"
28+
end
29+
30+
ENV["STAGEHAND_BASE_URL"] ||= ENV["STAGEHAND_API_URL"]
31+
end
32+
33+
def self.find_env_path
34+
current = File.expand_path(Dir.pwd)
35+
loop do
36+
candidate = File.join(current, "examples", ".env")
37+
return candidate if File.exist?(candidate)
38+
39+
parent = File.dirname(current)
40+
return nil if parent == current
41+
42+
current = parent
43+
end
44+
end
45+
end

examples/local_browser_example.rb

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
require "bundler/setup"
55
require "stagehand"
66

7-
# Local mode runs the embedded Stagehand server and uses a local Chrome/Chromium.
8-
# Set MODEL_API_KEY before running this script.
7+
require_relative "env"
8+
ExampleEnv.load!
99
model_key = ENV["MODEL_API_KEY"].to_s
1010
if model_key.empty?
1111
warn "Set MODEL_API_KEY to run the local example."
@@ -17,6 +17,40 @@
1717
server: "local"
1818
)
1919

20+
def print_stream_event(label, event)
21+
case event.type
22+
when :log
23+
puts("[#{label}] log: #{event.data.message}")
24+
when :system
25+
status = event.data.status
26+
if event.data.respond_to?(:error) && event.data.error
27+
puts("[#{label}] system #{status}: #{event.data.error}")
28+
elsif event.data.respond_to?(:result) && !event.data.result.nil?
29+
puts("[#{label}] system #{status}: #{event.data.result}")
30+
else
31+
puts("[#{label}] system #{status}")
32+
end
33+
else
34+
puts("[#{label}] event: #{event.inspect}")
35+
end
36+
end
37+
38+
def stream_with_result(label, stream)
39+
puts("#{label} stream:")
40+
result = nil
41+
stream.each do |event|
42+
print_stream_event(label, event)
43+
if event.type == :system && event.data.respond_to?(:result) && !event.data.result.nil?
44+
result = event.data.result
45+
end
46+
if event.type == :system && event.data.respond_to?(:status) && event.data.status == :error
47+
error_message = event.data.respond_to?(:error) && event.data.error ? event.data.error : "unknown error"
48+
raise("#{label} stream error: #{error_message}")
49+
end
50+
end
51+
result
52+
end
53+
2054
session_id = nil
2155

2256
begin
@@ -35,21 +69,25 @@
3569
client.sessions.navigate(session_id, url: "https://example.com")
3670
puts("Navigated to example.com")
3771

38-
observe_response = client.sessions.observe(
72+
observe_stream = client.sessions.observe_streaming(
3973
session_id,
4074
instruction: "Find all clickable links on this page"
4175
)
42-
puts("Found #{observe_response.data.result.length} possible actions")
76+
observe_result = stream_with_result("Observe", observe_stream)
77+
observe_actions = observe_result || []
78+
puts("Found #{observe_actions.length} possible actions")
4379

44-
action = observe_response.data.result.first
80+
action = observe_actions.first
4581
act_input = action ? action.to_h.merge(method: "click") : "Click the 'Learn more' link"
46-
act_response = client.sessions.act(
82+
act_stream = client.sessions.act_streaming(
4783
session_id,
4884
input: act_input
4985
)
50-
puts("Act completed: #{act_response.data.result[:message]}")
86+
act_result = stream_with_result("Act", act_stream)
87+
act_message = act_result.is_a?(Hash) ? (act_result[:message] || act_result["message"]) : act_result
88+
puts("Act completed: #{act_message}")
5189

52-
extract_response = client.sessions.extract(
90+
extract_stream = client.sessions.extract_streaming(
5391
session_id,
5492
instruction: "extract the main heading and any links on this page",
5593
schema: {
@@ -60,9 +98,10 @@
6098
}
6199
}
62100
)
63-
puts("Extracted: #{extract_response.data.result}")
101+
extract_result = stream_with_result("Extract", extract_stream)
102+
puts("Extracted: #{extract_result}")
64103

65-
execute_response = client.sessions.execute(
104+
execute_stream = client.sessions.execute_streaming(
66105
session_id,
67106
execute_options: {
68107
instruction: "Click on the 'Learn more' link if available and report the page title",
@@ -76,8 +115,13 @@
76115
cua: false
77116
}
78117
)
79-
puts("Agent completed: #{execute_response.data.result[:message]}")
80-
puts("Agent success: #{execute_response.data.result[:success]}")
118+
execute_result = stream_with_result("Execute", execute_stream)
119+
execute_message = execute_result.is_a?(Hash) ? (execute_result[:message] || execute_result["message"]) : execute_result
120+
execute_success = execute_result.is_a?(Hash) ? (execute_result[:success] || execute_result["success"]) : nil
121+
execute_actions = execute_result.is_a?(Hash) ? (execute_result[:actions] || execute_result["actions"]) : nil
122+
puts("Agent completed: #{execute_message}")
123+
puts("Agent success: #{execute_success}")
124+
puts("Agent actions taken: #{execute_actions&.length || 0}")
81125
ensure
82126
client.sessions.end_(session_id) if session_id
83127
client.close

examples/local_browser_playwright_example.rb

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,16 @@
55
require "bundler/setup"
66
require "stagehand"
77

8-
# Example: Using Playwright with Stagehand local mode (local browser).
9-
#
10-
# Prerequisites:
11-
# - Set MODEL_API_KEY or OPENAI_API_KEY environment variable
12-
# - Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID (can be any value in local mode)
13-
# - Install Playwright (outside this gem):
14-
# gem install playwright-ruby-client
15-
# npm install playwright
16-
# ./node_modules/.bin/playwright install chromium
17-
#
18-
# Run:
19-
# bundle exec ruby examples/local_browser_playwright_example.rb
20-
8+
require_relative "env"
9+
ExampleEnv.load!
2110
begin
2211
require("playwright")
2312
rescue LoadError
2413
warn("Playwright is not installed. Run: gem install playwright-ruby-client")
2514
exit(1)
2615
end
2716

28-
model_key = ENV["MODEL_API_KEY"] || ENV["OPENAI_API_KEY"]
17+
model_key = ENV["MODEL_API_KEY"]
2918
browserbase_api_key = ENV["BROWSERBASE_API_KEY"].to_s
3019
browserbase_project_id = ENV["BROWSERBASE_PROJECT_ID"].to_s
3120

examples/local_playwright_example.rb

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,8 @@
77
require "net/http"
88
require "stagehand"
99

10-
# Example: Using Playwright with Stagehand local mode
11-
#
12-
# This example mirrors the Python Playwright flow: launch the browser with Playwright,
13-
# connect Stagehand to the same browser via CDP, and target the existing page by frame_id.
14-
#
15-
# Prerequisites:
16-
# - Set MODEL_API_KEY or OPENAI_API_KEY environment variable
17-
# - Chrome/Chromium installed locally
18-
# - Install Playwright (outside this gem):
19-
# gem install playwright-ruby-client
20-
# npm install playwright
21-
# ./node_modules/.bin/playwright install chromium
22-
#
23-
# Run:
24-
# bundle exec ruby examples/local_playwright_example.rb
25-
10+
require_relative "env"
11+
ExampleEnv.load!
2612
begin
2713
require("playwright")
2814
rescue LoadError
@@ -69,9 +55,43 @@ def fetch_page_target_id(port, page_url)
6955
target_id
7056
end
7157

72-
model_key = ENV["MODEL_API_KEY"] || ENV["OPENAI_API_KEY"]
58+
def print_stream_event(label, event)
59+
case event.type
60+
when :log
61+
puts("[#{label}] log: #{event.data.message}")
62+
when :system
63+
status = event.data.status
64+
if event.data.respond_to?(:error) && event.data.error
65+
puts("[#{label}] system #{status}: #{event.data.error}")
66+
elsif event.data.respond_to?(:result) && !event.data.result.nil?
67+
puts("[#{label}] system #{status}: #{event.data.result}")
68+
else
69+
puts("[#{label}] system #{status}")
70+
end
71+
else
72+
puts("[#{label}] event: #{event.inspect}")
73+
end
74+
end
75+
76+
def stream_with_result(label, stream)
77+
puts("#{label} stream:")
78+
result = nil
79+
stream.each do |event|
80+
print_stream_event(label, event)
81+
if event.type == :system && event.data.respond_to?(:result) && !event.data.result.nil?
82+
result = event.data.result
83+
end
84+
if event.type == :system && event.data.respond_to?(:status) && event.data.status == :error
85+
error_message = event.data.respond_to?(:error) && event.data.error ? event.data.error : "unknown error"
86+
raise("#{label} stream error: #{error_message}")
87+
end
88+
end
89+
result
90+
end
91+
92+
model_key = ENV["MODEL_API_KEY"]
7393
if model_key.to_s.empty?
74-
warn "Set MODEL_API_KEY (or OPENAI_API_KEY) to run the local example."
94+
warn "Set MODEL_API_KEY to run the local example."
7595
exit 1
7696
end
7797

@@ -110,23 +130,27 @@ def fetch_page_target_id(port, page_url)
110130
session_id = start_response.data.session_id
111131
puts("Session started: #{session_id}")
112132

113-
observe_response = client.sessions.observe(
133+
observe_stream = client.sessions.observe_streaming(
114134
session_id,
115135
frame_id: page_target_id,
116136
instruction: "Find all clickable links on this page"
117137
)
118-
puts("Found #{observe_response.data.result.length} possible actions")
138+
observe_result = stream_with_result("Observe", observe_stream)
139+
observe_actions = observe_result || []
140+
puts("Found #{observe_actions.length} possible actions")
119141

120-
action = observe_response.data.result.first
142+
action = observe_actions.first
121143
act_input = action ? action.to_h.merge(method: "click") : "Click the 'Learn more' link"
122-
act_response = client.sessions.act(
144+
act_stream = client.sessions.act_streaming(
123145
session_id,
124146
frame_id: page_target_id,
125147
input: act_input
126148
)
127-
puts("Act completed: #{act_response.data.result[:message]}")
149+
act_result = stream_with_result("Act", act_stream)
150+
act_message = act_result.is_a?(Hash) ? (act_result[:message] || act_result["message"]) : act_result
151+
puts("Act completed: #{act_message}")
128152

129-
extract_response = client.sessions.extract(
153+
extract_stream = client.sessions.extract_streaming(
130154
session_id,
131155
frame_id: page_target_id,
132156
instruction: "Extract the main heading and any links on this page",
@@ -138,9 +162,10 @@ def fetch_page_target_id(port, page_url)
138162
}
139163
}
140164
)
141-
puts("Extracted: #{extract_response.data.result}")
165+
extract_result = stream_with_result("Extract", extract_stream)
166+
puts("Extracted: #{extract_result}")
142167

143-
execute_response = client.sessions.execute(
168+
execute_stream = client.sessions.execute_streaming(
144169
session_id,
145170
frame_id: page_target_id,
146171
execute_options: {
@@ -155,8 +180,13 @@ def fetch_page_target_id(port, page_url)
155180
cua: false
156181
}
157182
)
158-
puts("Agent completed: #{execute_response.data.result[:message]}")
159-
puts("Agent success: #{execute_response.data.result[:success]}")
183+
execute_result = stream_with_result("Execute", execute_stream)
184+
execute_message = execute_result.is_a?(Hash) ? (execute_result[:message] || execute_result["message"]) : execute_result
185+
execute_success = execute_result.is_a?(Hash) ? (execute_result[:success] || execute_result["success"]) : nil
186+
execute_actions = execute_result.is_a?(Hash) ? (execute_result[:actions] || execute_result["actions"]) : nil
187+
puts("Agent completed: #{execute_message}")
188+
puts("Agent success: #{execute_success}")
189+
puts("Agent actions taken: #{execute_actions&.length || 0}")
160190

161191
page.wait_for_load_state(state: "domcontentloaded")
162192
page.screenshot(path: "screenshot_local_playwright.png", fullPage: true)

0 commit comments

Comments
 (0)