diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 911c623..d11c8fc 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.19.3" + ".": "3.20.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index ac9e78a..391cde3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-b969ce378479c79ee64c05127c0ed6c6ce2edbee017ecd037242fb618a5ebc9f.yml -openapi_spec_hash: a24aabaa5214effb679808b7f2be0ad4 -config_hash: a962ae71493deb11a1c903256fb25386 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-6f6bfb81d092f30a5e2005328c97d61b9ea36132bb19e9e79e55294b9534ce20.yml +openapi_spec_hash: f3fc1e3688a38dc2c28f7178f7d534e5 +config_hash: 1fb12ae9b478488bc1e56bfbdc210b01 diff --git a/CHANGELOG.md b/CHANGELOG.md index 951b097..8fd0ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 3.20.0 (2026-05-06) + +Full Changelog: [v3.19.3...v3.20.0](https://github.com/browserbase/stagehand-ruby/compare/v3.19.3...v3.20.0) + +### Features + +* [feat]: add `ignoreSelectors` to `extract()` ([db24a9a](https://github.com/browserbase/stagehand-ruby/commit/db24a9ab45dc464d2ad88f9b50d39952c4c76801)) +* [STG-1798] feat: support Browserbase verified sessions ([9647eb3](https://github.com/browserbase/stagehand-ruby/commit/9647eb3a4df86cc9d7aedb2f0a9ddd0babd39565)) +* [STG-1808] Deprecate Browserbase project ID ([7af1c21](https://github.com/browserbase/stagehand-ruby/commit/7af1c21a07e7163ffb0b86f6c199989b750e0365)) +* Bedrock auth passthrough ([5cb6ecb](https://github.com/browserbase/stagehand-ruby/commit/5cb6ecbe621e894dc6c5aec379fdca1183eec4fc)) +* remove experimental requirement on agent variables ([#2079](https://github.com/browserbase/stagehand-ruby/issues/2079)) ([6f106d7](https://github.com/browserbase/stagehand-ruby/commit/6f106d74feb1b3f036433ec67dda5e4c395f434e)) +* Revert "[STG-1573] Add providerOptions for extensible model auth ([#1822](https://github.com/browserbase/stagehand-ruby/issues/1822))" ([0c83a09](https://github.com/browserbase/stagehand-ruby/commit/0c83a0978116f8fc11a4edf78586f3d61b8af5e8)) +* support setting headers via env ([5515959](https://github.com/browserbase/stagehand-ruby/commit/5515959505685e62f9324aebf470a295dd6d8292)) + + +### Bug Fixes + +* avoid gzip buffering during streaming ([23c3eef](https://github.com/browserbase/stagehand-ruby/commit/23c3eef17497edbf3ec03049d12c548607112c8d)) +* multipart encoding for file arrays ([149b303](https://github.com/browserbase/stagehand-ruby/commit/149b303eb54f4c9af037a5e073a2d6c92d959b44)) + + +### Chores + +* **internal:** more robust bootstrap script ([69c050a](https://github.com/browserbase/stagehand-ruby/commit/69c050a467b5fcab904331bd30bd18d46cee8cae)) + ## 3.19.3 (2026-04-03) Full Changelog: [v3.18.0...v3.19.3](https://github.com/browserbase/stagehand-ruby/compare/v3.18.0...v3.19.3) diff --git a/Gemfile.lock b/Gemfile.lock index a02c554..4490644 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - stagehand (3.19.3) + stagehand (3.20.0) cgi connection_pool diff --git a/README.md b/README.md index 4efcdcc..a55a073 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,6 @@ require "playwright" client = Stagehand::Client.new( browserbase_api_key: ENV["BROWSERBASE_API_KEY"], - browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], model_api_key: ENV["MODEL_API_KEY"], server: "remote" ) @@ -162,10 +161,10 @@ client.sessions.end_(session_id) Set your environment variables (from `examples/.env.example`): -- `STAGEHAND_API_URL` - `MODEL_API_KEY` - `BROWSERBASE_API_KEY` -- `BROWSERBASE_PROJECT_ID` + +`STAGEHAND_API_URL` is optional and defaults to the hosted Stagehand API. `STAGEHAND_BASE_URL` remains supported as a deprecated fallback when `STAGEHAND_API_URL` is unset. ```bash cp examples/.env.example examples/.env diff --git a/examples/.env.example b/examples/.env.example index 6272bb0..0033442 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -1,4 +1,2 @@ -STAGEHAND_API_URL=https://api.stagehand.browserbase.com MODEL_API_KEY=sk-proj-your-llm-api-key-here BROWSERBASE_API_KEY=bb_live_your_api_key_here -BROWSERBASE_PROJECT_ID=your-bb-project-uuid-here diff --git a/examples/env.rb b/examples/env.rb index fa3a3e3..518aca8 100644 --- a/examples/env.rb +++ b/examples/env.rb @@ -2,10 +2,8 @@ module ExampleEnv REQUIRED_KEYS = %w[ - STAGEHAND_API_URL MODEL_API_KEY BROWSERBASE_API_KEY - BROWSERBASE_PROJECT_ID ].freeze def self.load! @@ -23,11 +21,9 @@ def self.load! end missing = REQUIRED_KEYS.select { |key| ENV[key].to_s.empty? } - unless missing.empty? - raise "Missing required env vars: #{missing.join(', ')} (from examples/.env)" - end + return if missing.empty? - ENV["STAGEHAND_BASE_URL"] ||= ENV["STAGEHAND_API_URL"] + raise "Missing required env vars: #{missing.join(', ')} (from examples/.env)" end def self.find_env_path diff --git a/examples/local_browser_playwright_example.rb b/examples/local_browser_playwright_example.rb index 23acf37..ae96c09 100755 --- a/examples/local_browser_playwright_example.rb +++ b/examples/local_browser_playwright_example.rb @@ -16,12 +16,10 @@ model_key = ENV["MODEL_API_KEY"] browserbase_api_key = ENV["BROWSERBASE_API_KEY"].to_s -browserbase_project_id = ENV["BROWSERBASE_PROJECT_ID"].to_s missing = [] missing << "MODEL_API_KEY" if model_key.to_s.empty? missing << "BROWSERBASE_API_KEY" if browserbase_api_key.empty? -missing << "BROWSERBASE_PROJECT_ID" if browserbase_project_id.empty? unless missing.empty? warn("Set #{missing.join(', ')} to run the local Playwright example.") @@ -60,7 +58,6 @@ def stream_with_result(label, stream) client = Stagehand::Client.new( browserbase_api_key: browserbase_api_key, - browserbase_project_id: browserbase_project_id, model_api_key: model_key, server: "local" ) diff --git a/examples/local_server_multiregion_browser_example.rb b/examples/local_server_multiregion_browser_example.rb index d891c3f..ecd888a 100755 --- a/examples/local_server_multiregion_browser_example.rb +++ b/examples/local_server_multiregion_browser_example.rb @@ -7,12 +7,10 @@ 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? @@ -22,7 +20,6 @@ client = Stagehand::Client.new( browserbase_api_key: browserbase_api_key, - browserbase_project_id: browserbase_project_id, model_api_key: model_key, server: "local" ) diff --git a/examples/remote_browser_example.rb b/examples/remote_browser_example.rb index c4bf716..443e973 100755 --- a/examples/remote_browser_example.rb +++ b/examples/remote_browser_example.rb @@ -7,12 +7,10 @@ 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? @@ -22,7 +20,6 @@ client = Stagehand::Client.new( browserbase_api_key: browserbase_api_key, - browserbase_project_id: browserbase_project_id, model_api_key: model_key ) diff --git a/examples/remote_browser_playwright_example.rb b/examples/remote_browser_playwright_example.rb index 0b886d8..d263880 100755 --- a/examples/remote_browser_playwright_example.rb +++ b/examples/remote_browser_playwright_example.rb @@ -16,12 +16,10 @@ model_key = ENV["MODEL_API_KEY"] browserbase_api_key = ENV["BROWSERBASE_API_KEY"].to_s -browserbase_project_id = ENV["BROWSERBASE_PROJECT_ID"].to_s missing = [] missing << "MODEL_API_KEY" if model_key.to_s.empty? missing << "BROWSERBASE_API_KEY" if browserbase_api_key.empty? -missing << "BROWSERBASE_PROJECT_ID" if browserbase_project_id.empty? unless missing.empty? warn("Set #{missing.join(', ')} to run the remote Playwright example.") @@ -83,7 +81,6 @@ def stream_with_result(label, stream) client = Stagehand::Client.new( browserbase_api_key: browserbase_api_key, - browserbase_project_id: browserbase_project_id, model_api_key: model_key, server: "remote" ) diff --git a/lib/stagehand/client.rb b/lib/stagehand/client.rb index b923e72..e13a67f 100644 --- a/lib/stagehand/client.rb +++ b/lib/stagehand/client.rb @@ -19,7 +19,8 @@ class Client < Stagehand::Internal::Transport::BaseClient # @return [String] attr_reader :browserbase_api_key - # Your [Browserbase Project ID](https://www.browserbase.com/settings) + # Deprecated. Browserbase API keys are now project-scoped, so this value is accepted for + # backwards compatibility and ignored. # @return [String] attr_reader :browserbase_project_id @@ -34,7 +35,7 @@ class Client < Stagehand::Internal::Transport::BaseClient # # @return [Hash{String=>String}] private def auth_headers - {**bb_api_key_auth, **bb_project_id_auth, **llm_model_api_key_auth} + {**bb_api_key_auth, **llm_model_api_key_auth} end # @api private @@ -48,7 +49,7 @@ class Client < Stagehand::Internal::Transport::BaseClient # # @return [Hash{String=>String}] private def bb_project_id_auth - {"x-bb-project-id" => @browserbase_project_id} + {} end # @api private @@ -63,8 +64,8 @@ class Client < Stagehand::Internal::Transport::BaseClient # @param browserbase_api_key [String, nil] Your [Browserbase API Key](https://www.browserbase.com/settings) Defaults to # `ENV["BROWSERBASE_API_KEY"]` # - # @param browserbase_project_id [String, nil] Your [Browserbase Project ID](https://www.browserbase.com/settings) Defaults to - # `ENV["BROWSERBASE_PROJECT_ID"]` + # @param browserbase_project_id [String, nil] Deprecated. Browserbase API keys are now project-scoped, so + # this value is accepted for backwards compatibility and ignored. # # @param model_api_key [String, nil] Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) # Defaults to `ENV["MODEL_API_KEY"]` @@ -72,7 +73,8 @@ class Client < Stagehand::Internal::Transport::BaseClient # @param server [String] Server mode to use ("remote" or "local"). Defaults to "remote" # # @param base_url [String, nil] Override the default base URL for the API, e.g., - # `"https://api.example.com/v2/"`. Defaults to `ENV["STAGEHAND_BASE_URL"]` + # `"https://api.example.com/v2/"`. Defaults to `ENV["STAGEHAND_API_URL"]`, + # then `ENV["STAGEHAND_BASE_URL"]` # # @param max_retries [Integer] Max number of retries to attempt after a failed retryable request. # @@ -83,10 +85,10 @@ class Client < Stagehand::Internal::Transport::BaseClient # @param max_retry_delay [Float] def initialize( browserbase_api_key: ENV["BROWSERBASE_API_KEY"], - browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], + browserbase_project_id: nil, model_api_key: ENV["MODEL_API_KEY"], server: "remote", - base_url: ENV["STAGEHAND_BASE_URL"], + base_url: ENV["STAGEHAND_API_URL"] || ENV["STAGEHAND_BASE_URL"], max_retries: self.class::DEFAULT_MAX_RETRIES, timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS, initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY, @@ -99,15 +101,24 @@ def initialize( raise ArgumentError, "browserbase_api_key is required, and can be set via environ: \"BROWSERBASE_API_KEY\"" end - if browserbase_project_id.nil? - raise ArgumentError, - "browserbase_project_id is required, and can be set via environ: \"BROWSERBASE_PROJECT_ID\"" - end if model_api_key.nil? raise ArgumentError, "model_api_key is required, and can be set via environ: \"MODEL_API_KEY\"" end + headers = {} + custom_headers_env = ENV["STAGEHAND_CUSTOM_HEADERS"] + unless custom_headers_env.nil? + parsed = {} + custom_headers_env.split("\n").each do |line| + colon = line.index(":") + unless colon.nil? + parsed[line[0...colon].strip] = line[(colon + 1)..].strip + end + end + headers = parsed.merge(headers) + end + @browserbase_api_key = browserbase_api_key.to_s @browserbase_project_id = browserbase_project_id.to_s @model_api_key = model_api_key.to_s @@ -117,7 +128,8 @@ def initialize( timeout: timeout, max_retries: max_retries, initial_retry_delay: initial_retry_delay, - max_retry_delay: max_retry_delay + max_retry_delay: max_retry_delay, + headers: headers ) @sessions = Stagehand::Resources::Sessions.new(client: self) diff --git a/lib/stagehand/internal/util.rb b/lib/stagehand/internal/util.rb index 4e9a51e..3ac0058 100644 --- a/lib/stagehand/internal/util.rb +++ b/lib/stagehand/internal/util.rb @@ -610,6 +610,7 @@ def encode_query_params(query) # # @return [Array(String, Enumerable)] private def encode_multipart_streaming(body) + # rubocop:disable Style/CaseEquality # RFC 1521 Section 7.2.1 says we should have 70 char maximum for boundary length boundary = SecureRandom.urlsafe_base64(46) @@ -619,7 +620,7 @@ def encode_query_params(query) in Hash body.each do |key, val| case val - in Array if val.all? { primitive?(_1) } + in Array if val.all? { primitive?(_1) || Stagehand::Internal::Type::FileInput === _1 } val.each do |v| write_multipart_chunk(y, boundary: boundary, key: key, val: v, closing: closing) end @@ -635,6 +636,7 @@ def encode_query_params(query) fused_io = fused_enum(strio) { closing.each(&:call) } [boundary, fused_io] + # rubocop:enable Style/CaseEquality end # @api private diff --git a/lib/stagehand/local.rb b/lib/stagehand/local.rb index 410ae55..01c47ab 100644 --- a/lib/stagehand/local.rb +++ b/lib/stagehand/local.rb @@ -50,7 +50,6 @@ def self.ensure_browserbase_credentials!(client:, params:) missing = [] missing << "browserbase_api_key" if client.browserbase_api_key.to_s.empty? - missing << "browserbase_project_id" if client.browserbase_project_id.to_s.empty? return if missing.empty? message = @@ -213,10 +212,9 @@ def ensure_executable(path) end class ServerManager - def initialize(model_api_key:, browserbase_api_key:, browserbase_project_id:) + def initialize(model_api_key:, browserbase_api_key:) @model_api_key = model_api_key @browserbase_api_key = browserbase_api_key - @browserbase_project_id = browserbase_project_id @host = DEFAULT_HOST @port = 0 @mutex = Mutex.new @@ -335,9 +333,6 @@ def build_env(host:, port:) if @browserbase_api_key.to_s != "" env["BROWSERBASE_API_KEY"] = @browserbase_api_key end - if @browserbase_project_id.to_s != "" - env["BROWSERBASE_PROJECT_ID"] = @browserbase_project_id - end env end @@ -376,8 +371,6 @@ def initialize(server: "remote", **kwargs) kwargs[:base_url] = base_url.nil? ? "http://#{DEFAULT_HOST}" : base_url kwargs[:browserbase_api_key] = kwargs[:browserbase_api_key] || ENV["BROWSERBASE_API_KEY"] || "" - kwargs[:browserbase_project_id] = - kwargs[:browserbase_project_id] || ENV["BROWSERBASE_PROJECT_ID"] || "" end super(**kwargs) @@ -386,8 +379,7 @@ def initialize(server: "remote", **kwargs) @local_server_manager = Stagehand::Local::ServerManager.new( model_api_key: @model_api_key, - browserbase_api_key: @browserbase_api_key, - browserbase_project_id: @browserbase_project_id + browserbase_api_key: @browserbase_api_key ) end @@ -421,8 +413,7 @@ def bb_api_key_auth end def bb_project_id_auth - return {} if @browserbase_project_id.to_s.empty? - super + {} end end diff --git a/lib/stagehand/models/session_execute_params.rb b/lib/stagehand/models/session_execute_params.rb index 6185efe..549fbc5 100644 --- a/lib/stagehand/models/session_execute_params.rb +++ b/lib/stagehand/models/session_execute_params.rb @@ -210,7 +210,14 @@ class ExecuteOptions < Stagehand::Internal::Type::BaseModel # @return [Boolean, nil] optional :use_search, Stagehand::Internal::Type::Boolean, api_name: :useSearch - # @!method initialize(instruction:, highlight_cursor: nil, max_steps: nil, tool_timeout: nil, use_search: nil) + # @!attribute variables + # Variables available to the agent via %variableName% syntax in supported tools + # + # @return [Hash{Symbol=>String, Float, Boolean, Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3}, nil] + optional :variables, + -> { Stagehand::Internal::Type::HashOf[union: Stagehand::SessionExecuteParams::ExecuteOptions::Variable] } + + # @!method initialize(instruction:, highlight_cursor: nil, max_steps: nil, tool_timeout: nil, use_search: nil, variables: nil) # @param instruction [String] Natural language instruction for the agent # # @param highlight_cursor [Boolean] Whether to visually highlight the cursor during execution @@ -220,6 +227,54 @@ class ExecuteOptions < Stagehand::Internal::Type::BaseModel # @param tool_timeout [Float] Timeout in milliseconds for each agent tool call # # @param use_search [Boolean] Whether to enable the web search tool powered by Browserbase Search API + # + # @param variables [Hash{Symbol=>String, Float, Boolean, Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3}] Variables available to the agent via %variableName% syntax in supported tools + + module Variable + extend Stagehand::Internal::Type::Union + + variant String + + variant Float + + variant Stagehand::Internal::Type::Boolean + + variant -> { Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3 } + + class UnionMember3 < Stagehand::Internal::Type::BaseModel + # @!attribute value + # + # @return [String, Float, Boolean] + required :value, + union: -> { Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::Value } + + # @!attribute description + # + # @return [String, nil] + optional :description, String + + # @!method initialize(value:, description: nil) + # @param value [String, Float, Boolean] + # @param description [String] + + # @see Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3#value + module Value + extend Stagehand::Internal::Type::Union + + variant String + + variant Float + + variant Stagehand::Internal::Type::Boolean + + # @!method self.variants + # @return [Array(String, Float, Boolean)] + end + end + + # @!method self.variants + # @return [Array(String, Float, Boolean, Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3)] + end end # Whether to stream the response via SSE diff --git a/lib/stagehand/models/session_extract_params.rb b/lib/stagehand/models/session_extract_params.rb index 9836d1e..1b304fb 100644 --- a/lib/stagehand/models/session_extract_params.rb +++ b/lib/stagehand/models/session_extract_params.rb @@ -60,6 +60,12 @@ class SessionExtractParams < Stagehand::Internal::Type::BaseModel # @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}] class Options < Stagehand::Internal::Type::BaseModel + # @!attribute ignore_selectors + # Selectors for elements and subtrees that should be excluded from extraction + # + # @return [Array, nil] + optional :ignore_selectors, Stagehand::Internal::Type::ArrayOf[String], api_name: :ignoreSelectors + # @!attribute model # Model configuration object or model name string (e.g., 'openai/gpt-5-nano') # @@ -78,7 +84,9 @@ class Options < Stagehand::Internal::Type::BaseModel # @return [Float, nil] optional :timeout, Float - # @!method initialize(model: nil, selector: nil, timeout: nil) + # @!method initialize(ignore_selectors: nil, model: nil, selector: nil, timeout: nil) + # @param ignore_selectors [Array] Selectors for elements and subtrees that should be excluded from extraction + # # @param model [Stagehand::Models::ModelConfig, String] Model configuration object or model name string (e.g., 'openai/gpt-5-nano') # # @param selector [String] CSS selector to scope extraction to a specific element diff --git a/lib/stagehand/models/session_start_params.rb b/lib/stagehand/models/session_start_params.rb index 754ec05..41f061e 100644 --- a/lib/stagehand/models/session_start_params.rb +++ b/lib/stagehand/models/session_start_params.rb @@ -353,6 +353,10 @@ class BrowserbaseSessionCreateParams < Stagehand::Internal::Type::BaseModel optional :keep_alive, Stagehand::Internal::Type::Boolean, api_name: :keepAlive # @!attribute project_id + # @deprecated + # + # Deprecated. Browserbase API keys are now project-scoped, so this field is no + # longer required. # # @return [String, nil] optional :project_id, String, api_name: :projectId @@ -380,13 +384,24 @@ class BrowserbaseSessionCreateParams < Stagehand::Internal::Type::BaseModel api_name: :userMetadata # @!method initialize(browser_settings: nil, extension_id: nil, keep_alive: nil, project_id: nil, proxies: nil, region: nil, timeout: nil, user_metadata: nil) + # Some parameter documentations has been truncated, see + # {Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams} for more + # details. + # # @param browser_settings [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings] + # # @param extension_id [String] + # # @param keep_alive [Boolean] - # @param project_id [String] + # + # @param project_id [String] Deprecated. Browserbase API keys are now project-scoped, so this field is no lon + # # @param proxies [Boolean, Array] + # # @param region [Symbol, Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::Region] + # # @param timeout [Float] + # # @param user_metadata [Hash{Symbol=>Object}] # @see Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams#browser_settings @@ -401,6 +416,16 @@ class BrowserSettings < Stagehand::Internal::Type::BaseModel # @return [Boolean, nil] optional :block_ads, Stagehand::Internal::Type::Boolean, api_name: :blockAds + # @!attribute captcha_image_selector + # + # @return [String, nil] + optional :captcha_image_selector, String, api_name: :captchaImageSelector + + # @!attribute captcha_input_selector + # + # @return [String, nil] + optional :captcha_input_selector, String, api_name: :captchaInputSelector + # @!attribute context # # @return [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context, nil] @@ -423,6 +448,12 @@ class BrowserSettings < Stagehand::Internal::Type::BaseModel # @return [Boolean, nil] optional :log_session, Stagehand::Internal::Type::Boolean, api_name: :logSession + # @!attribute os + # + # @return [Symbol, Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os, nil] + optional :os, + enum: -> { Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os } + # @!attribute record_session # # @return [Boolean, nil] @@ -433,21 +464,30 @@ class BrowserSettings < Stagehand::Internal::Type::BaseModel # @return [Boolean, nil] optional :solve_captchas, Stagehand::Internal::Type::Boolean, api_name: :solveCaptchas + # @!attribute verified + # + # @return [Boolean, nil] + optional :verified, Stagehand::Internal::Type::Boolean + # @!attribute viewport # # @return [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport, nil] optional :viewport, -> { Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport } - # @!method initialize(advanced_stealth: nil, block_ads: nil, context: nil, extension_id: nil, fingerprint: nil, log_session: nil, record_session: nil, solve_captchas: nil, viewport: nil) + # @!method initialize(advanced_stealth: nil, block_ads: nil, captcha_image_selector: nil, captcha_input_selector: nil, context: nil, extension_id: nil, fingerprint: nil, log_session: nil, os: nil, record_session: nil, solve_captchas: nil, verified: nil, viewport: nil) # @param advanced_stealth [Boolean] # @param block_ads [Boolean] + # @param captcha_image_selector [String] + # @param captcha_input_selector [String] # @param context [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context] # @param extension_id [String] # @param fingerprint [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint] # @param log_session [Boolean] + # @param os [Symbol, Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os] # @param record_session [Boolean] # @param solve_captchas [Boolean] + # @param verified [Boolean] # @param viewport [Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport] # @see Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings#context @@ -596,6 +636,20 @@ class Screen < Stagehand::Internal::Type::BaseModel end end + # @see Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings#os + module Os + extend Stagehand::Internal::Type::Enum + + WINDOWS = :windows + MAC = :mac + LINUX = :linux + MOBILE = :mobile + TABLET = :tablet + + # @!method self.values + # @return [Array] + end + # @see Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings#viewport class Viewport < Stagehand::Internal::Type::BaseModel # @!attribute height diff --git a/lib/stagehand/resources/sessions.rb b/lib/stagehand/resources/sessions.rb index 2e4f6e3..7a50cee 100644 --- a/lib/stagehand/resources/sessions.rb +++ b/lib/stagehand/resources/sessions.rb @@ -77,8 +77,11 @@ def act_streaming(id, params) path: ["v1/sessions/%1$s/act", id], headers: { "accept" => "text/event-stream", + "accept-encoding" => "identity", **parsed.slice(*header_params.keys) - }.transform_keys(header_params), + }.transform_keys( + header_params + ), body: parsed.except(*header_params.keys), stream: Stagehand::Internal::Stream, model: Stagehand::StreamEvent, @@ -193,8 +196,11 @@ def execute_streaming(id, params) path: ["v1/sessions/%1$s/agentExecute", id], headers: { "accept" => "text/event-stream", + "accept-encoding" => "identity", **parsed.slice(*header_params.keys) - }.transform_keys(header_params), + }.transform_keys( + header_params + ), body: parsed.except(*header_params.keys), stream: Stagehand::Internal::Stream, model: Stagehand::StreamEvent, @@ -279,8 +285,11 @@ def extract_streaming(id, params = {}) path: ["v1/sessions/%1$s/extract", id], headers: { "accept" => "text/event-stream", + "accept-encoding" => "identity", **parsed.slice(*header_params.keys) - }.transform_keys(header_params), + }.transform_keys( + header_params + ), body: parsed.except(*header_params.keys), stream: Stagehand::Internal::Stream, model: Stagehand::StreamEvent, @@ -397,8 +406,11 @@ def observe_streaming(id, params = {}) path: ["v1/sessions/%1$s/observe", id], headers: { "accept" => "text/event-stream", + "accept-encoding" => "identity", **parsed.slice(*header_params.keys) - }.transform_keys(header_params), + }.transform_keys( + header_params + ), body: parsed.except(*header_params.keys), stream: Stagehand::Internal::Stream, model: Stagehand::StreamEvent, diff --git a/lib/stagehand/version.rb b/lib/stagehand/version.rb index b4bbb16..2fbf718 100644 --- a/lib/stagehand/version.rb +++ b/lib/stagehand/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Stagehand - VERSION = "3.19.3" + VERSION = "3.20.0" end diff --git a/rbi/stagehand/client.rbi b/rbi/stagehand/client.rbi index 3997c40..e569fde 100644 --- a/rbi/stagehand/client.rbi +++ b/rbi/stagehand/client.rbi @@ -14,7 +14,7 @@ module Stagehand sig { returns(String) } attr_reader :browserbase_api_key - # Your [Browserbase Project ID](https://www.browserbase.com/settings) + # Deprecated. Browserbase API keys are now project-scoped, so this value is accepted for backwards compatibility and ignored. sig { returns(String) } attr_reader :browserbase_project_id @@ -63,17 +63,17 @@ module Stagehand # Your [Browserbase API Key](https://www.browserbase.com/settings) Defaults to # `ENV["BROWSERBASE_API_KEY"]` browserbase_api_key: ENV["BROWSERBASE_API_KEY"], - # Your [Browserbase Project ID](https://www.browserbase.com/settings) Defaults to - # `ENV["BROWSERBASE_PROJECT_ID"]` - browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], + # Deprecated. Browserbase API keys are now project-scoped, so this value is accepted for backwards compatibility and ignored. + browserbase_project_id: nil, # Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) # Defaults to `ENV["MODEL_API_KEY"]` model_api_key: ENV["MODEL_API_KEY"], # Server mode to use ("remote" or "local"). Defaults to "remote" server: "remote", # Override the default base URL for the API, e.g., - # `"https://api.example.com/v2/"`. Defaults to `ENV["STAGEHAND_BASE_URL"]` - base_url: ENV["STAGEHAND_BASE_URL"], + # `"https://api.example.com/v2/"`. Defaults to `ENV["STAGEHAND_API_URL"]`, + # then `ENV["STAGEHAND_BASE_URL"]` + base_url: ENV["STAGEHAND_API_URL"] || ENV["STAGEHAND_BASE_URL"], # Max number of retries to attempt after a failed retryable request. max_retries: Stagehand::Client::DEFAULT_MAX_RETRIES, timeout: Stagehand::Client::DEFAULT_TIMEOUT_IN_SECONDS, diff --git a/rbi/stagehand/models/session_execute_params.rbi b/rbi/stagehand/models/session_execute_params.rbi index d0577ae..4fafc4d 100644 --- a/rbi/stagehand/models/session_execute_params.rbi +++ b/rbi/stagehand/models/session_execute_params.rbi @@ -401,13 +401,57 @@ module Stagehand sig { params(use_search: T::Boolean).void } attr_writer :use_search + # Variables available to the agent via %variableName% syntax in supported tools + sig do + returns( + T.nilable( + T::Hash[ + Symbol, + T.any( + String, + Float, + T::Boolean, + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3 + ) + ] + ) + ) + end + attr_reader :variables + + sig do + params( + variables: + T::Hash[ + Symbol, + T.any( + String, + Float, + T::Boolean, + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::OrHash + ) + ] + ).void + end + attr_writer :variables + sig do params( instruction: String, highlight_cursor: T::Boolean, max_steps: Float, tool_timeout: Float, - use_search: T::Boolean + use_search: T::Boolean, + variables: + T::Hash[ + Symbol, + T.any( + String, + Float, + T::Boolean, + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::OrHash + ) + ] ).returns(T.attached_class) end def self.new( @@ -420,7 +464,9 @@ module Stagehand # Timeout in milliseconds for each agent tool call tool_timeout: nil, # Whether to enable the web search tool powered by Browserbase Search API - use_search: nil + use_search: nil, + # Variables available to the agent via %variableName% syntax in supported tools + variables: nil ) end @@ -431,12 +477,107 @@ module Stagehand highlight_cursor: T::Boolean, max_steps: Float, tool_timeout: Float, - use_search: T::Boolean + use_search: T::Boolean, + variables: + T::Hash[ + Symbol, + T.any( + String, + Float, + T::Boolean, + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3 + ) + ] } ) end def to_hash end + + module Variable + extend Stagehand::Internal::Type::Union + + Variants = + T.type_alias do + T.any( + String, + Float, + T::Boolean, + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3 + ) + end + + class UnionMember3 < Stagehand::Internal::Type::BaseModel + OrHash = + T.type_alias do + T.any( + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3, + Stagehand::Internal::AnyHash + ) + end + + sig do + returns( + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::Value::Variants + ) + end + attr_accessor :value + + sig { returns(T.nilable(String)) } + attr_reader :description + + sig { params(description: String).void } + attr_writer :description + + sig do + params( + value: + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::Value::Variants, + description: String + ).returns(T.attached_class) + end + def self.new(value:, description: nil) + end + + sig do + override.returns( + { + value: + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::Value::Variants, + description: String + } + ) + end + def to_hash + end + + module Value + extend Stagehand::Internal::Type::Union + + Variants = T.type_alias { T.any(String, Float, T::Boolean) } + + sig do + override.returns( + T::Array[ + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::Value::Variants + ] + ) + end + def self.variants + end + end + end + + sig do + override.returns( + T::Array[ + Stagehand::SessionExecuteParams::ExecuteOptions::Variable::Variants + ] + ) + end + def self.variants + end + end end # Whether to stream the response via SSE diff --git a/rbi/stagehand/models/session_extract_params.rbi b/rbi/stagehand/models/session_extract_params.rbi index e23207c..2fc2eb4 100644 --- a/rbi/stagehand/models/session_extract_params.rbi +++ b/rbi/stagehand/models/session_extract_params.rbi @@ -111,6 +111,13 @@ module Stagehand ) end + # Selectors for elements and subtrees that should be excluded from extraction + sig { returns(T.nilable(T::Array[String])) } + attr_reader :ignore_selectors + + sig { params(ignore_selectors: T::Array[String]).void } + attr_writer :ignore_selectors + # Model configuration object or model name string (e.g., 'openai/gpt-5-nano') sig { returns(T.nilable(T.any(Stagehand::ModelConfig, String))) } attr_reader :model @@ -136,12 +143,15 @@ module Stagehand sig do params( + ignore_selectors: T::Array[String], model: T.any(Stagehand::ModelConfig::OrHash, String), selector: String, timeout: Float ).returns(T.attached_class) end def self.new( + # Selectors for elements and subtrees that should be excluded from extraction + ignore_selectors: nil, # Model configuration object or model name string (e.g., 'openai/gpt-5-nano') model: nil, # CSS selector to scope extraction to a specific element @@ -154,6 +164,7 @@ module Stagehand sig do override.returns( { + ignore_selectors: T::Array[String], model: T.any(Stagehand::ModelConfig, String), selector: String, timeout: Float diff --git a/rbi/stagehand/models/session_start_params.rbi b/rbi/stagehand/models/session_start_params.rbi index 67dc4c0..91d1192 100644 --- a/rbi/stagehand/models/session_start_params.rbi +++ b/rbi/stagehand/models/session_start_params.rbi @@ -680,6 +680,8 @@ module Stagehand sig { params(keep_alive: T::Boolean).void } attr_writer :keep_alive + # Deprecated. Browserbase API keys are now project-scoped, so this field is no + # longer required. sig { returns(T.nilable(String)) } attr_reader :project_id @@ -751,6 +753,8 @@ module Stagehand browser_settings: nil, extension_id: nil, keep_alive: nil, + # Deprecated. Browserbase API keys are now project-scoped, so this field is no + # longer required. project_id: nil, proxies: nil, region: nil, @@ -800,6 +804,18 @@ module Stagehand sig { params(block_ads: T::Boolean).void } attr_writer :block_ads + sig { returns(T.nilable(String)) } + attr_reader :captcha_image_selector + + sig { params(captcha_image_selector: String).void } + attr_writer :captcha_image_selector + + sig { returns(T.nilable(String)) } + attr_reader :captcha_input_selector + + sig { params(captcha_input_selector: String).void } + attr_writer :captcha_input_selector + sig do returns( T.nilable( @@ -846,6 +862,23 @@ module Stagehand sig { params(log_session: T::Boolean).void } attr_writer :log_session + sig do + returns( + T.nilable( + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::OrSymbol + ) + ) + end + attr_reader :os + + sig do + params( + os: + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::OrSymbol + ).void + end + attr_writer :os + sig { returns(T.nilable(T::Boolean)) } attr_reader :record_session @@ -858,6 +891,12 @@ module Stagehand sig { params(solve_captchas: T::Boolean).void } attr_writer :solve_captchas + sig { returns(T.nilable(T::Boolean)) } + attr_reader :verified + + sig { params(verified: T::Boolean).void } + attr_writer :verified + sig do returns( T.nilable( @@ -879,14 +918,19 @@ module Stagehand params( advanced_stealth: T::Boolean, block_ads: T::Boolean, + captcha_image_selector: String, + captcha_input_selector: String, context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context::OrHash, extension_id: String, fingerprint: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint::OrHash, log_session: T::Boolean, + os: + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::OrSymbol, record_session: T::Boolean, solve_captchas: T::Boolean, + verified: T::Boolean, viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport::OrHash ).returns(T.attached_class) @@ -894,12 +938,16 @@ module Stagehand def self.new( advanced_stealth: nil, block_ads: nil, + captcha_image_selector: nil, + captcha_input_selector: nil, context: nil, extension_id: nil, fingerprint: nil, log_session: nil, + os: nil, record_session: nil, solve_captchas: nil, + verified: nil, viewport: nil ) end @@ -909,14 +957,19 @@ module Stagehand { advanced_stealth: T::Boolean, block_ads: T::Boolean, + captcha_image_selector: String, + captcha_input_selector: String, context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context, extension_id: String, fingerprint: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint, log_session: T::Boolean, + os: + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::OrSymbol, record_session: T::Boolean, solve_captchas: T::Boolean, + verified: T::Boolean, viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport } @@ -1348,6 +1401,55 @@ module Stagehand end end + module Os + extend Stagehand::Internal::Type::Enum + + TaggedSymbol = + T.type_alias do + T.all( + Symbol, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os + ) + end + OrSymbol = T.type_alias { T.any(Symbol, String) } + + WINDOWS = + T.let( + :windows, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ) + MAC = + T.let( + :mac, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ) + LINUX = + T.let( + :linux, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ) + MOBILE = + T.let( + :mobile, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ) + TABLET = + T.let( + :tablet, + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ) + + sig do + override.returns( + T::Array[ + Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Os::TaggedSymbol + ] + ) + end + def self.values + end + end + class Viewport < Stagehand::Internal::Type::BaseModel OrHash = T.type_alias do diff --git a/scripts/bootstrap b/scripts/bootstrap index 3487864..a5e1b80 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd -- "$(dirname -- "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response diff --git a/sig/stagehand/models/session_execute_params.rbs b/sig/stagehand/models/session_execute_params.rbs index 167e110..068d8eb 100644 --- a/sig/stagehand/models/session_execute_params.rbs +++ b/sig/stagehand/models/session_execute_params.rbs @@ -163,7 +163,8 @@ module Stagehand highlight_cursor: bool, max_steps: Float, tool_timeout: Float, - use_search: bool + use_search: bool, + variables: ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] } class ExecuteOptions < Stagehand::Internal::Type::BaseModel @@ -185,12 +186,19 @@ module Stagehand def use_search=: (bool) -> bool + attr_reader variables: ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable]? + + def variables=: ( + ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] + ) -> ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] + def initialize: ( instruction: String, ?highlight_cursor: bool, ?max_steps: Float, ?tool_timeout: Float, - ?use_search: bool + ?use_search: bool, + ?variables: ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] ) -> void def to_hash: -> { @@ -198,8 +206,53 @@ module Stagehand highlight_cursor: bool, max_steps: Float, tool_timeout: Float, - use_search: bool + use_search: bool, + variables: ::Hash[Symbol, Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] } + + type variable = + String + | Float + | bool + | Stagehand::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3 + + module Variable + extend Stagehand::Internal::Type::Union + + type union_member3 = + { + value: Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::value, + description: String + } + + class UnionMember3 < Stagehand::Internal::Type::BaseModel + attr_accessor value: Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::value + + attr_reader description: String? + + def description=: (String) -> String + + def initialize: ( + value: Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::value, + ?description: String + ) -> void + + def to_hash: -> { + value: Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::value, + description: String + } + + type value = String | Float | bool + + module Value + extend Stagehand::Internal::Type::Union + + def self?.variants: -> ::Array[Stagehand::Models::SessionExecuteParams::ExecuteOptions::Variable::UnionMember3::value] + end + end + + def self?.variants: -> ::Array[Stagehand::Models::SessionExecuteParams::ExecuteOptions::variable] + end end type x_stream_response = :true | :false diff --git a/sig/stagehand/models/session_extract_params.rbs b/sig/stagehand/models/session_extract_params.rbs index 2dd8f92..aedaaca 100644 --- a/sig/stagehand/models/session_extract_params.rbs +++ b/sig/stagehand/models/session_extract_params.rbs @@ -61,12 +61,17 @@ module Stagehand type options = { + ignore_selectors: ::Array[String], model: Stagehand::Models::SessionExtractParams::Options::model, selector: String, timeout: Float } class Options < Stagehand::Internal::Type::BaseModel + attr_reader ignore_selectors: ::Array[String]? + + def ignore_selectors=: (::Array[String]) -> ::Array[String] + attr_reader model: Stagehand::Models::SessionExtractParams::Options::model? def model=: ( @@ -82,12 +87,14 @@ module Stagehand def timeout=: (Float) -> Float def initialize: ( + ?ignore_selectors: ::Array[String], ?model: Stagehand::Models::SessionExtractParams::Options::model, ?selector: String, ?timeout: Float ) -> void def to_hash: -> { + ignore_selectors: ::Array[String], model: Stagehand::Models::SessionExtractParams::Options::model, selector: String, timeout: Float diff --git a/sig/stagehand/models/session_start_params.rbs b/sig/stagehand/models/session_start_params.rbs index c9cae4c..af70a2b 100644 --- a/sig/stagehand/models/session_start_params.rbs +++ b/sig/stagehand/models/session_start_params.rbs @@ -450,12 +450,16 @@ module Stagehand { advanced_stealth: bool, block_ads: bool, + captcha_image_selector: String, + captcha_input_selector: String, context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context, extension_id: String, fingerprint: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint, log_session: bool, + os: Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os, record_session: bool, solve_captchas: bool, + verified: bool, viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport } @@ -468,6 +472,14 @@ module Stagehand def block_ads=: (bool) -> bool + attr_reader captcha_image_selector: String? + + def captcha_image_selector=: (String) -> String + + attr_reader captcha_input_selector: String? + + def captcha_input_selector=: (String) -> String + attr_reader context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context? def context=: ( @@ -488,6 +500,12 @@ module Stagehand def log_session=: (bool) -> bool + attr_reader os: Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os? + + def os=: ( + Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os + ) -> Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os + attr_reader record_session: bool? def record_session=: (bool) -> bool @@ -496,6 +514,10 @@ module Stagehand def solve_captchas=: (bool) -> bool + attr_reader verified: bool? + + def verified=: (bool) -> bool + attr_reader viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport? def viewport=: ( @@ -505,24 +527,32 @@ module Stagehand def initialize: ( ?advanced_stealth: bool, ?block_ads: bool, + ?captcha_image_selector: String, + ?captcha_input_selector: String, ?context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context, ?extension_id: String, ?fingerprint: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint, ?log_session: bool, + ?os: Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os, ?record_session: bool, ?solve_captchas: bool, + ?verified: bool, ?viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport ) -> void def to_hash: -> { advanced_stealth: bool, block_ads: bool, + captcha_image_selector: String, + captcha_input_selector: String, context: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Context, extension_id: String, fingerprint: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Fingerprint, log_session: bool, + os: Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os, record_session: bool, solve_captchas: bool, + verified: bool, viewport: Stagehand::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::Viewport } @@ -693,6 +723,20 @@ module Stagehand end end + type os = :windows | :mac | :linux | :mobile | :tablet + + module Os + extend Stagehand::Internal::Type::Enum + + WINDOWS: :windows + MAC: :mac + LINUX: :linux + MOBILE: :mobile + TABLET: :tablet + + def self?.values: -> ::Array[Stagehand::Models::SessionStartParams::BrowserbaseSessionCreateParams::BrowserSettings::os] + end + type viewport = { height: Float, width: Float } class Viewport < Stagehand::Internal::Type::BaseModel diff --git a/test/stagehand/client_test.rb b/test/stagehand/client_test.rb index 0c4a0a8..4b96ac0 100644 --- a/test/stagehand/client_test.rb +++ b/test/stagehand/client_test.rb @@ -34,6 +34,85 @@ def test_raises_on_missing_non_nullable_opts assert_match(/is required/, e.message) end + def test_client_base_url_from_stagehand_api_url + with_env("STAGEHAND_API_URL" => "http://localhost:5000/from/api/env", "STAGEHAND_BASE_URL" => nil) do + stagehand = + Stagehand::Client.new( + browserbase_api_key: "My Browserbase API Key", + browserbase_project_id: "My Browserbase Project ID", + model_api_key: "My Model API Key" + ) + + assert_equal("http://localhost:5000/from/api/env", stagehand.base_url.to_s) + end + end + + def test_client_base_url_from_legacy_stagehand_base_url + with_env("STAGEHAND_API_URL" => nil, "STAGEHAND_BASE_URL" => "http://localhost:5000/from/base/env") do + stagehand = + Stagehand::Client.new( + browserbase_api_key: "My Browserbase API Key", + browserbase_project_id: "My Browserbase Project ID", + model_api_key: "My Model API Key" + ) + + assert_equal("http://localhost:5000/from/base/env", stagehand.base_url.to_s) + end + end + + def test_client_base_url_prefers_stagehand_api_url + with_env( + "STAGEHAND_API_URL" => "http://localhost:5000/from/api/env", + "STAGEHAND_BASE_URL" => "http://localhost:5000/from/base/env" + ) do + stagehand = + Stagehand::Client.new( + browserbase_api_key: "My Browserbase API Key", + browserbase_project_id: "My Browserbase Project ID", + model_api_key: "My Model API Key" + ) + + assert_equal("http://localhost:5000/from/api/env", stagehand.base_url.to_s) + end + end + + def test_client_omits_project_id_header + captured_headers = nil + stub_request(:post, "http://localhost/v1/sessions/start") + .with do |request| + captured_headers = request.headers + true + end + .to_return_json(status: 200, body: {}) + + stagehand = + Stagehand::Client.new( + base_url: "http://localhost", + browserbase_api_key: "My Browserbase API Key", + browserbase_project_id: "My Browserbase Project ID", + model_api_key: "My Model API Key" + ) + + stagehand.sessions.start(model_name: "openai/gpt-5.4-mini") + + refute_nil(captured_headers) + refute_includes(captured_headers.keys.map(&:downcase), "x-bb-project-id") + end + + def with_env(vars) + old_values = vars.keys.to_h { |key| [key, ENV.fetch(key, nil)] } + + vars.each do |key, value| + value.nil? ? ENV.delete(key) : ENV[key] = value + end + + yield + ensure + old_values.each do |key, value| + value.nil? ? ENV.delete(key) : ENV[key] = value + end + end + def test_client_default_request_default_retry_attempts stub_request(:post, "http://localhost/v1/sessions/start").to_return_json(status: 500, body: {})