Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,59 @@
name: CI
on:
push:
branches-ignore:
- 'generated'
- 'codegen/**'
- 'integrated/**'
- 'stl-preview-head/**'
- 'stl-preview-base/**'
branches:
- '**'
- '!integrated/**'
- '!stl-preview-head/**'
- '!stl-preview-base/**'
- '!generated'
- '!codegen/**'
- 'codegen/stl/**'
pull_request:
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'

jobs:
build:
timeout-minutes: 10
name: build
permissions:
contents: read
id-token: write
runs-on: ${{ github.repository == 'stainless-sdks/stagehand-ruby' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: |-
github.repository == 'stainless-sdks/stagehand-ruby' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.fork) &&
(github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: false
- run: |-
bundle install

- name: Get GitHub OIDC Token
if: |-
github.repository == 'stainless-sdks/stagehand-ruby' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
uses: actions/github-script@v8
with:
script: core.setOutput('github_token', await core.getIDToken());

- name: Build and upload gem artifacts
if: |-
github.repository == 'stainless-sdks/stagehand-ruby' &&
!startsWith(github.ref, 'refs/heads/stl/')
env:
URL: https://pkg.stainless.com/s
AUTH: ${{ steps.github-oidc.outputs.github_token }}
SHA: ${{ github.sha }}
PACKAGE_NAME: stagehand
run: ./scripts/utils/upload-artifact.sh
lint:
timeout-minutes: 10
name: lint
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.ignore
.prism.log
.env
.stdy.log
.ruby-lsp/
.yardoc/
bin/tapioca
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.7.1"
".": "3.18.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-a4e672f457dd99336f4b2a113fd7c7c6c9db0941b38d57cff6e3641549a6c4ed.yml
openapi_spec_hash: eae9c8561e420db8e4d238c1e59617fb
config_hash: 2a565ad6662259a2e90fa5f1f5095525
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-bc309fd00fe0507f4cbe3bc77fa27d0fbffeaa6e71998778da34de42608a67e8.yml
openapi_spec_hash: 1db1af5c1b068bba1d652102f4454668
config_hash: d6c6f623d03971bdba921650e5eb7e5f
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## 3.18.0 (2026-03-25)

Full Changelog: [v3.7.1...v3.18.0](https://github.com/browserbase/stagehand-ruby/compare/v3.7.1...v3.18.0)

### Features

* [fix]: add `useSearch` & `toolTimeout` to stainless types ([e5bedb8](https://github.com/browserbase/stagehand-ruby/commit/e5bedb88ac67d34a6db440e470e3eb964205e558))
* [STG-1607] Yield finished SSE event instead of silently dropping it ([d5ffd2d](https://github.com/browserbase/stagehand-ruby/commit/d5ffd2d0ed6495673ef80ba0e1649bd1c1afa815))
* Add explicit SSE event names for local v3 streaming ([5be6d12](https://github.com/browserbase/stagehand-ruby/commit/5be6d1293763ecb7f0268884784475986de2915c))
* Add missing cdpHeaders field to v3 server openapi spec ([de6457f](https://github.com/browserbase/stagehand-ruby/commit/de6457f121764ffbadac51ec2a6103c27dbde338))
* Include LLM headers in ModelConfig ([23cb4ea](https://github.com/browserbase/stagehand-ruby/commit/23cb4ea1319287992f1174c2b8fa2eff5fdfc5c4))
* Revert broken finished SSE yield config ([1de4c00](https://github.com/browserbase/stagehand-ruby/commit/1de4c00c0b72936813fcd3e0499b51b1f135f33d))
* variables for observe ([a9d6970](https://github.com/browserbase/stagehand-ruby/commit/a9d6970e7f43f1d69eddb42a14f09fb7f9158b29))


### Chores

* **ci:** add build step ([ed6573f](https://github.com/browserbase/stagehand-ruby/commit/ed6573f2ddab4f5f51d1262480cb663da5e003d0))
* **ci:** skip lint on metadata-only changes ([079a802](https://github.com/browserbase/stagehand-ruby/commit/079a8028edf4e9dd09517363b0c1a3b16ed86865))
* **ci:** skip uploading artifacts on stainless-internal branches ([3a7e90b](https://github.com/browserbase/stagehand-ruby/commit/3a7e90bc8dd9f983dbbc7be16f997dd2dc02c3c0))
* **internal:** codegen related update ([f714af2](https://github.com/browserbase/stagehand-ruby/commit/f714af2d7d5b9b822362aa10f6f6facab3332eaf))
* **internal:** codegen related update ([ed9fab1](https://github.com/browserbase/stagehand-ruby/commit/ed9fab1f726c05ef48d4a55507b09212d7ced713))
* **internal:** tweak CI branches ([85b5b46](https://github.com/browserbase/stagehand-ruby/commit/85b5b4656c921f0784d05b27a3c4773b05b51cc6))
* **internal:** update gitignore ([b4bc8a8](https://github.com/browserbase/stagehand-ruby/commit/b4bc8a8ae60ff252080d4bfcd0fc6af26dc95e74))

## 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)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ GIT
PATH
remote: .
specs:
stagehand (3.7.1)
stagehand (3.18.0)
cgi
connection_pool

Expand Down
13 changes: 2 additions & 11 deletions lib/stagehand/internal/stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,10 @@ class Stream
#
# @return [Enumerable<generic<Elem>>]
private def iterator
# rubocop:disable Metrics/BlockLength
@iterator ||= Stagehand::Internal::Util.chain_fused(@stream) do |y|
consume = false

@stream.each do |msg|
next if consume

case msg
in {data: String => data} if data.start_with?("{\"data\":{\"status\":\"finished\"")
consume = true
next
in {data: String => data} if data.start_with?("error")
in {event: "error", data: String => data}
decoded = Kernel.then do
JSON.parse(data, symbolize_names: true)
rescue JSON::ParserError
Expand All @@ -41,15 +33,14 @@ class Stream
response: @response
)
raise err
in {event: nil, data: String => data}
in {event: "starting" | "connected" | "running" | "finished", data: String => data}
decoded = JSON.parse(data, symbolize_names: true)
unwrapped = Stagehand::Internal::Util.dig(decoded, @unwrap)
y << Stagehand::Internal::Type::Converter.coerce(@model, unwrapped)
else
end
end
end
# rubocop:enable Metrics/BlockLength
end
end
end
Expand Down
31 changes: 31 additions & 0 deletions lib/stagehand/internal/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,37 @@ def writable_enum(&blk)
JSONL_CONTENT = %r{^application/(:?x-(?:n|l)djson)|(:?(?:x-)?jsonl)}

class << self
# @api private
#
# @param query [Hash{Symbol=>Object}]
#
# @return [Hash{Symbol=>Object}]
def encode_query_params(query)
out = {}
query.each { write_query_param_element!(out, _1, _2) }
out
end

# @api private
#
# @param collection [Hash{Symbol=>Object}]
# @param key [String]
# @param element [Object]
#
# @return [nil]
private def write_query_param_element!(collection, key, element)
case element
in Hash
element.each do |name, value|
write_query_param_element!(collection, "#{key}[#{name}]", value)
end
in Array
collection[key] = element.map(&:to_s).join(",")
else
collection[key] = element.to_s
end
end

# @api private
#
# @param y [Enumerator::Yielder]
Expand Down
10 changes: 5 additions & 5 deletions lib/stagehand/local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def platform_tag

def binary_filename
platform, arch = platform_tag
name = "stagehand-server-#{platform}-#{arch}"
name = "stagehand-server-v3-#{platform}-#{arch}"
name += ".exe" if platform == "win32"
name
end
Expand Down Expand Up @@ -131,9 +131,9 @@ def home_dir

def resolve_version(version)
return fetch_latest_tag if version.empty? || version == "latest"
return version if version.start_with?("stagehand-server/")
return version if version.start_with?("stagehand-server-v3/")

"stagehand-server/#{version}"
"stagehand-server-v3/#{version}"
end

def fetch_latest_tag
Expand All @@ -148,9 +148,9 @@ def fetch_latest_tag
releases = JSON.parse(response.body.to_s)
releases.each do |release|
tag = release["tag_name"]
return tag if tag.is_a?(String) && tag.start_with?("stagehand-server/")
return tag if tag.is_a?(String) && tag.start_with?("stagehand-server-v3/")
end
raise "Failed to find stagehand-server release tag"
raise "Failed to find stagehand-server-v3 release tag"
end

def download_binary(tag, dest_path)
Expand Down
10 changes: 9 additions & 1 deletion lib/stagehand/models/model_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,27 @@ class ModelConfig < Stagehand::Internal::Type::BaseModel
# @return [String, nil]
optional :base_url, String, api_name: :baseURL

# @!attribute headers
# Custom headers sent with every request to the model provider
#
# @return [Hash{Symbol=>String}, nil]
optional :headers, Stagehand::Internal::Type::HashOf[String]

# @!attribute provider
# AI provider for the model (or provide a baseURL endpoint instead)
#
# @return [Symbol, Stagehand::Models::ModelConfig::Provider, nil]
optional :provider, enum: -> { Stagehand::ModelConfig::Provider }

# @!method initialize(model_name:, api_key: nil, base_url: nil, provider: nil)
# @!method initialize(model_name:, api_key: nil, base_url: nil, headers: nil, provider: nil)
# @param model_name [String] Model name string with provider prefix (e.g., 'openai/gpt-5-nano')
#
# @param api_key [String] API key for the model provider
#
# @param base_url [String] Base URL for the model provider
#
# @param headers [Hash{Symbol=>String}] Custom headers sent with every request to the model provider
#
# @param provider [Symbol, Stagehand::Models::ModelConfig::Provider] AI provider for the model (or provide a baseURL endpoint instead)

# AI provider for the model (or provide a baseURL endpoint instead)
Expand Down
68 changes: 63 additions & 5 deletions lib/stagehand/models/session_act_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ class SessionActParams < Stagehand::Internal::Type::BaseModel
extend Stagehand::Internal::Type::RequestParameters::Converter
include Stagehand::Internal::Type::RequestParameters

# @!attribute id
# Unique session identifier
#
# @return [String]
required :id, String

# @!attribute input
# Natural language instruction or Action object
#
Expand All @@ -32,7 +38,9 @@ class SessionActParams < Stagehand::Internal::Type::BaseModel
# @return [Symbol, Stagehand::Models::SessionActParams::XStreamResponse, nil]
optional :x_stream_response, enum: -> { Stagehand::SessionActParams::XStreamResponse }

# @!method initialize(input:, frame_id: nil, options: nil, x_stream_response: nil, request_options: {})
# @!method initialize(id:, input:, frame_id: nil, options: nil, x_stream_response: nil, request_options: {})
# @param id [String] Unique session identifier
#
# @param input [String, Stagehand::Models::Action] Natural language instruction or Action object
#
# @param frame_id [String, nil] Target frame ID for the action
Expand Down Expand Up @@ -70,17 +78,22 @@ class Options < Stagehand::Internal::Type::BaseModel
optional :timeout, Float

# @!attribute variables
# Variables to substitute in the action instruction
# Variables to substitute in the action instruction. Accepts flat primitives or {
# value, description? } objects.
#
# @return [Hash{Symbol=>String}, nil]
optional :variables, Stagehand::Internal::Type::HashOf[String]
# @return [Hash{Symbol=>String, Float, Boolean, Stagehand::Models::SessionActParams::Options::Variable::UnionMember3}, nil]
optional :variables,
-> { Stagehand::Internal::Type::HashOf[union: Stagehand::SessionActParams::Options::Variable] }

# @!method initialize(model: nil, timeout: nil, variables: nil)
# Some parameter documentations has been truncated, see
# {Stagehand::Models::SessionActParams::Options} for more details.
#
# @param model [Stagehand::Models::ModelConfig, String] Model configuration object or model name string (e.g., 'openai/gpt-5-nano')
#
# @param timeout [Float] Timeout in ms for the action
#
# @param variables [Hash{Symbol=>String}] Variables to substitute in the action instruction
# @param variables [Hash{Symbol=>String, Float, Boolean, Stagehand::Models::SessionActParams::Options::Variable::UnionMember3}] Variables to substitute in the action instruction. Accepts flat primitives or {

# Model configuration object or model name string (e.g., 'openai/gpt-5-nano')
#
Expand All @@ -95,6 +108,51 @@ module Model
# @!method self.variants
# @return [Array(Stagehand::Models::ModelConfig, String)]
end

module Variable
extend Stagehand::Internal::Type::Union

variant String

variant Float

variant Stagehand::Internal::Type::Boolean

variant -> { Stagehand::SessionActParams::Options::Variable::UnionMember3 }

class UnionMember3 < Stagehand::Internal::Type::BaseModel
# @!attribute value
#
# @return [String, Float, Boolean]
required :value, union: -> { Stagehand::SessionActParams::Options::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::SessionActParams::Options::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::SessionActParams::Options::Variable::UnionMember3)]
end
end

# Whether to stream the response via SSE
Expand Down
10 changes: 9 additions & 1 deletion lib/stagehand/models/session_end_params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ class SessionEndParams < Stagehand::Internal::Type::BaseModel
extend Stagehand::Internal::Type::RequestParameters::Converter
include Stagehand::Internal::Type::RequestParameters

# @!attribute id
# Unique session identifier
#
# @return [String]
required :id, String

# @!attribute x_stream_response
# Whether to stream the response via SSE
#
# @return [Symbol, Stagehand::Models::SessionEndParams::XStreamResponse, nil]
optional :x_stream_response, enum: -> { Stagehand::SessionEndParams::XStreamResponse }

# @!method initialize(x_stream_response: nil, request_options: {})
# @!method initialize(id:, x_stream_response: nil, request_options: {})
# @param id [String] Unique session identifier
#
# @param x_stream_response [Symbol, Stagehand::Models::SessionEndParams::XStreamResponse] Whether to stream the response via SSE
#
# @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}]
Expand Down
Loading