From ca96021ec6cd8957b9955442afcbc21573fe2a40 Mon Sep 17 00:00:00 2001 From: monadoid Date: Thu, 26 Feb 2026 10:57:57 -0700 Subject: [PATCH 1/4] STG-1293: add local server multiregion example --- ...ocal_server_multiregion_browser_example.rb | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100755 examples/local_server_multiregion_browser_example.rb diff --git a/examples/local_server_multiregion_browser_example.rb b/examples/local_server_multiregion_browser_example.rb new file mode 100755 index 0000000..d891c3f --- /dev/null +++ b/examples/local_server_multiregion_browser_example.rb @@ -0,0 +1,161 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "stagehand" + +require_relative "env" +ExampleEnv.load! +browserbase_api_key = ENV["BROWSERBASE_API_KEY"].to_s +browserbase_project_id = ENV["BROWSERBASE_PROJECT_ID"].to_s +model_key = ENV["MODEL_API_KEY"].to_s + +missing = [] +missing << "BROWSERBASE_API_KEY" if browserbase_api_key.empty? +missing << "BROWSERBASE_PROJECT_ID" if browserbase_project_id.empty? +missing << "MODEL_API_KEY" if model_key.empty? + +unless missing.empty? + warn "Set #{missing.join(', ')} to run the local server + multiregion Browserbase example." + exit 1 +end + +client = Stagehand::Client.new( + browserbase_api_key: browserbase_api_key, + browserbase_project_id: browserbase_project_id, + model_api_key: model_key, + server: "local" +) + +def print_stream_event(label, event) + case event.type + when :log + puts("[#{label}] log: #{event.data.message}") + when :system + status = event.data.status + if event.data.respond_to?(:error) && event.data.error + puts("[#{label}] system #{status}: #{event.data.error}") + elsif event.data.respond_to?(:result) && !event.data.result.nil? + puts("[#{label}] system #{status}: #{event.data.result}") + else + puts("[#{label}] system #{status}") + end + else + puts("[#{label}] event: #{event.inspect}") + end +end + +def stream_with_result(label, stream) + puts("#{label} stream:") + result = nil + stream.each do |event| + print_stream_event(label, event) + if event.type == :system && event.data.respond_to?(:result) && !event.data.result.nil? + result = event.data.result + end + if event.type == :system && event.data.respond_to?(:status) && event.data.status == :error + error_message = event.data.respond_to?(:error) && event.data.error ? event.data.error : "unknown error" + raise("#{label} stream error: #{error_message}") + end + end + result +end + +session_id = nil + +begin + start_response = client.sessions.start( + model_name: "anthropic/claude-sonnet-4-6", + browser: {type: :browserbase}, + browserbase_session_create_params: { + region: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::Region::EU_CENTRAL_1 + } + ) + session_id = start_response.data.session_id + puts("Session started: #{session_id}") + + client.sessions.navigate(session_id, url: "https://news.ycombinator.com") + puts("Navigated to Hacker News") + + observe_stream = client.sessions.observe_streaming( + session_id, + instruction: "find the link to view comments for the top post" + ) + + observe_result = stream_with_result("Observe", observe_stream) + actions = observe_result || [] + puts("Found #{actions.length} possible actions") + + action = actions.first + unless action + warn("No actions found") + exit(1) + end + + puts("Acting on: #{action.description}") + + act_stream = client.sessions.act_streaming( + session_id, + input: action.to_h.merge(method: "click") + ) + act_result = stream_with_result("Act", act_stream) + act_message = act_result.is_a?(Hash) ? (act_result[:message] || act_result["message"]) : act_result + puts("Act completed: #{act_message}") + + extract_stream = client.sessions.extract_streaming( + session_id, + instruction: "extract the text of the top comment on this page", + schema: { + type: "object", + properties: { + comment_text: { + type: "string", + description: "The text content of the top comment" + }, + author: { + type: "string", + description: "The username of the comment author" + } + }, + required: ["comment_text"] + } + ) + extract_result = stream_with_result("Extract", extract_stream) + puts("Extracted data: #{extract_result}") + + extracted_data = extract_result + author = extracted_data.is_a?(Hash) ? extracted_data[:author] : nil + author ||= "unknown" + puts("Looking up profile for author: #{author}") + + instruction = [ + "Find any personal website, GitHub, or LinkedIn for the Hacker News user '#{author}'.", + "Click their username to open the profile and look for shared links." + ].join(" ") + + execute_stream = client.sessions.execute_streaming( + session_id, + execute_options: { + instruction: instruction, + max_steps: 15 + }, + agent_config: { + model: Stagehand::ModelConfig.new( + model_name: "anthropic/claude-opus-4-6", + api_key: model_key + ), + cua: false + } + ) + + execute_result = stream_with_result("Execute", execute_stream) + execute_message = execute_result.is_a?(Hash) ? (execute_result[:message] || execute_result["message"]) : execute_result + execute_success = execute_result.is_a?(Hash) ? (execute_result[:success] || execute_result["success"]) : nil + execute_actions = execute_result.is_a?(Hash) ? (execute_result[:actions] || execute_result["actions"]) : nil + puts("Agent completed: #{execute_message}") + puts("Agent success: #{execute_success}") + puts("Agent actions taken: #{execute_actions&.length || 0}") +ensure + client.sessions.end_(session_id) if session_id + client.close +end From 8b8d4b40911ab6a95f405c9f02bb8b1e62a5ad33 Mon Sep 17 00:00:00 2001 From: monadoid Date: Thu, 26 Feb 2026 11:13:52 -0700 Subject: [PATCH 2/4] STG-1293: document multiregion example --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5729f31..24d6ea3 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ Examples and dependencies: - `examples/local_playwright_example.rb`: `playwright-ruby-client` + Playwright browsers - `examples/local_watir_example.rb`: `watir` +Multiregion support: see `examples/local_server_multiregion_browser_example.rb`. + Install dependencies for the example you want to run, then execute it: ```bash From ed2c2c28400498332f0742b2616fd14a66646690 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 06:54:55 +0000 Subject: [PATCH 3/4] fix: properly mock time in ruby ci tests --- test/stagehand/client_test.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/stagehand/client_test.rb b/test/stagehand/client_test.rb index fcb4bd0..4e6154f 100644 --- a/test/stagehand/client_test.rb +++ b/test/stagehand/client_test.rb @@ -133,9 +133,11 @@ def test_client_retry_after_seconds end def test_client_retry_after_date + time_now = Time.now + stub_request(:post, "http://localhost/v1/sessions/start").to_return_json( status: 500, - headers: {"retry-after" => (Time.now + 10).httpdate}, + headers: {"retry-after" => (time_now + 10).httpdate}, body: {} ) @@ -148,11 +150,11 @@ def test_client_retry_after_date max_retries: 1 ) + Thread.current.thread_variable_set(:time_now, time_now) assert_raises(Stagehand::Errors::InternalServerError) do - Thread.current.thread_variable_set(:time_now, Time.now) stagehand.sessions.start(model_name: "openai/gpt-4o") - Thread.current.thread_variable_set(:time_now, nil) end + Thread.current.thread_variable_set(:time_now, nil) assert_requested(:any, /./, times: 2) assert_in_delta(10, Thread.current.thread_variable_get(:mock_sleep).last, 1.0) From a31b92d73af0029eedfebb8da4d83c226ec66d7e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 06:55:12 +0000 Subject: [PATCH 4/4] release: 3.7.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ Gemfile.lock | 2 +- lib/stagehand/version.rb | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 27d2fbb..c0786b3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.7.0" + ".": "3.7.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f73f8..6cb5fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 3.7.1 (2026-02-27) + +Full Changelog: [v3.7.0...v3.7.1](https://github.com/browserbase/stagehand-ruby/compare/v3.7.0...v3.7.1) + +### Bug Fixes + +* properly mock time in ruby ci tests ([ed2c2c2](https://github.com/browserbase/stagehand-ruby/commit/ed2c2c28400498332f0742b2616fd14a66646690)) + ## 3.7.0 (2026-02-25) Full Changelog: [v3.6.1...v3.7.0](https://github.com/browserbase/stagehand-ruby/compare/v3.6.1...v3.7.0) diff --git a/Gemfile.lock b/Gemfile.lock index cbd7020..f179e2c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - stagehand (3.7.0) + stagehand (3.7.1) cgi connection_pool diff --git a/lib/stagehand/version.rb b/lib/stagehand/version.rb index 8f2aab7..0886c7c 100644 --- a/lib/stagehand/version.rb +++ b/lib/stagehand/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Stagehand - VERSION = "3.7.0" + VERSION = "3.7.1" end