diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..da59f99 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.4.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 604325f..5dba58c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-e96507dd78e76fccc77ba7fb09704da127ead6f4d73ea854e9b2150e90787ff4.yml -openapi_spec_hash: 0c2548b8fdd6de6789b19123e69609c1 -config_hash: c3abb41dbe698d59b3bf12f393013d54 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-f7d6b6489159f611a2bfdc267ce0a6fc0455bed1ffa0c310044baaa5d8381b9b.yml +openapi_spec_hash: cd88d8068abfde8382da0bed674e440c +config_hash: 5c69fb596588b8ace08203858518c149 diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e6df9..16b9b31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.4.0 (2025-12-19) + +Full Changelog: [v0.3.0...v0.4.0](https://github.com/browserbase/stagehand-ruby/compare/v0.3.0...v0.4.0) + +### Features + +* **api:** manual updates ([c93a988](https://github.com/browserbase/stagehand-ruby/commit/c93a988374918f841643a84145b3008970b665a7)) + + +### Bug Fixes + +* issue where json.parse errors when receiving HTTP 204 with nobody ([fc3e31e](https://github.com/browserbase/stagehand-ruby/commit/fc3e31e00944a7fdf89e367e281bc0510847d15e)) + ## 0.3.0 (2025-12-17) Full Changelog: [v0.2.0...v0.3.0](https://github.com/browserbase/stagehand-ruby/compare/v0.2.0...v0.3.0) diff --git a/Gemfile.lock b/Gemfile.lock index bf9ed01..0569df8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ GIT PATH remote: . specs: - stagehand (0.3.0) + stagehand (0.4.0) connection_pool GEM diff --git a/README.md b/README.md index 6e1d972..b459cad 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ To use this gem, install via Bundler by adding the following to your application ```ruby -gem "stagehand", "~> 0.3.0" +gem "stagehand", "~> 0.4.0" ``` diff --git a/lib/stagehand.rb b/lib/stagehand.rb index fd329b8..b056332 100644 --- a/lib/stagehand.rb +++ b/lib/stagehand.rb @@ -44,6 +44,7 @@ require_relative "stagehand/internal/type/hash_of" require_relative "stagehand/internal/type/base_model" require_relative "stagehand/internal/type/base_page" +require_relative "stagehand/internal/type/base_stream" require_relative "stagehand/internal/type/request_parameters" require_relative "stagehand/internal" require_relative "stagehand/request_options" @@ -52,6 +53,7 @@ require_relative "stagehand/internal/transport/base_client" require_relative "stagehand/internal/transport/pooled_net_requester" require_relative "stagehand/client" +require_relative "stagehand/internal/stream" require_relative "stagehand/models/action" require_relative "stagehand/models/model_config" require_relative "stagehand/models/session_act_params" @@ -68,5 +70,6 @@ require_relative "stagehand/models/session_observe_response" require_relative "stagehand/models/session_start_params" require_relative "stagehand/models/session_start_response" +require_relative "stagehand/models/stream_event" require_relative "stagehand/models" require_relative "stagehand/resources/sessions" diff --git a/lib/stagehand/internal/stream.rb b/lib/stagehand/internal/stream.rb new file mode 100644 index 0000000..37a63a2 --- /dev/null +++ b/lib/stagehand/internal/stream.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Stagehand + module Internal + # @generic Elem + # + # @example + # stream.each do |event| + # puts(event) + # end + class Stream + include Stagehand::Internal::Type::BaseStream + + # @api private + # + # @return [Enumerable>] + 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?("finished") + consume = true + next + in {data: String => data} if data.start_with?("error") + decoded = Kernel.then do + JSON.parse(data, symbolize_names: true) + rescue JSON::ParserError + data + end + err = Stagehand::Errors::APIStatusError.for( + url: @url, + status: @status, + headers: @headers, + body: decoded, + request: nil, + response: @response + ) + raise err + in {event: nil, 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 +end diff --git a/lib/stagehand/internal/transport/base_client.rb b/lib/stagehand/internal/transport/base_client.rb index 871681a..54200c4 100644 --- a/lib/stagehand/internal/transport/base_client.rb +++ b/lib/stagehand/internal/transport/base_client.rb @@ -549,7 +549,9 @@ def inspect ) ), page: T.nilable(T::Class[Stagehand::Internal::Type::BasePage[Stagehand::Internal::Type::BaseModel]]), - stream: T.nilable(T::Class[T.anything]), + stream: T.nilable( + T::Class[Stagehand::Internal::Type::BaseStream[T.anything, Stagehand::Internal::Type::BaseModel]] + ), model: T.nilable(Stagehand::Internal::Type::Converter::Input), options: T.nilable(Stagehand::RequestOptions::OrHash) } diff --git a/lib/stagehand/internal/type/base_stream.rb b/lib/stagehand/internal/type/base_stream.rb new file mode 100644 index 0000000..9de74f6 --- /dev/null +++ b/lib/stagehand/internal/type/base_stream.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module Stagehand + module Internal + module Type + # @api private + # + # @generic Elem + # + # This module provides a base implementation for streaming responses in the SDK. + # + # @see https://rubyapi.org/3.2/o/enumerable + module BaseStream + include Enumerable + + # @return [Integer] + attr_reader :status + + # @return [Hash{String=>String}] + attr_reader :headers + + # @api public + # + # @return [void] + def close = Stagehand::Internal::Util.close_fused!(@iterator) + + # @api private + # + # @return [Enumerable>] + private def iterator = (raise NotImplementedError) + + # @api public + # + # @param blk [Proc] + # + # @yieldparam [generic] + # @return [void] + def each(&blk) + unless block_given? + raise ArgumentError.new("A block must be given to ##{__method__}") + end + @iterator.each(&blk) + end + + # @api public + # + # @return [Enumerator>] + def to_enum = @iterator + + alias_method :enum_for, :to_enum + + # @api private + # + # @param model [Class, Stagehand::Internal::Type::Converter] + # @param url [URI::Generic] + # @param status [Integer] + # @param headers [Hash{String=>String}] + # @param response [Net::HTTPResponse] + # @param unwrap [Symbol, Integer, Array, Proc] + # @param stream [Enumerable] + def initialize(model:, url:, status:, headers:, response:, unwrap:, stream:) + @model = model + @url = url + @status = status + @headers = headers + @response = response + @unwrap = unwrap + @stream = stream + @iterator = iterator + end + + # @api private + # + # @return [String] + def inspect + model = Stagehand::Internal::Type::Converter.inspect(@model, depth: 1) + + "#<#{self.class}[#{model}]:0x#{object_id.to_s(16)}>" + end + end + end + end +end diff --git a/lib/stagehand/internal/util.rb b/lib/stagehand/internal/util.rb index 528508f..1698a6f 100644 --- a/lib/stagehand/internal/util.rb +++ b/lib/stagehand/internal/util.rb @@ -657,7 +657,8 @@ def force_charset!(content_type, text:) def decode_content(headers, stream:, suppress_error: false) case (content_type = headers["content-type"]) in Stagehand::Internal::Util::JSON_CONTENT - json = stream.to_a.join + return nil if (json = stream.to_a.join).empty? + begin JSON.parse(json, symbolize_names: true) rescue JSON::ParserError => e @@ -667,7 +668,11 @@ def decode_content(headers, stream:, suppress_error: false) in Stagehand::Internal::Util::JSONL_CONTENT lines = decode_lines(stream) chain_fused(lines) do |y| - lines.each { y << JSON.parse(_1, symbolize_names: true) } + lines.each do + next if _1.empty? + + y << JSON.parse(_1, symbolize_names: true) + end end in %r{^text/event-stream} lines = decode_lines(stream) diff --git a/lib/stagehand/models.rb b/lib/stagehand/models.rb index 307492e..463b760 100644 --- a/lib/stagehand/models.rb +++ b/lib/stagehand/models.rb @@ -56,4 +56,6 @@ module Stagehand SessionObserveParams = Stagehand::Models::SessionObserveParams SessionStartParams = Stagehand::Models::SessionStartParams + + StreamEvent = Stagehand::Models::StreamEvent end diff --git a/lib/stagehand/models/session_act_params.rb b/lib/stagehand/models/session_act_params.rb index efa8b67..83f4fed 100644 --- a/lib/stagehand/models/session_act_params.rb +++ b/lib/stagehand/models/session_act_params.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#act + # + # @see Stagehand::Resources::Sessions#act_streaming class SessionActParams < Stagehand::Internal::Type::BaseModel extend Stagehand::Internal::Type::RequestParameters::Converter include Stagehand::Internal::Type::RequestParameters @@ -10,7 +12,7 @@ class SessionActParams < Stagehand::Internal::Type::BaseModel # @!attribute input # Natural language instruction or Action object # - # @return [String, Stagehand::Models::SessionActParams::Input::ActionInput] + # @return [String, Stagehand::Models::Action] required :input, union: -> { Stagehand::SessionActParams::Input } # @!attribute frame_id @@ -49,7 +51,7 @@ class SessionActParams < Stagehand::Internal::Type::BaseModel optional :x_stream_response, enum: -> { Stagehand::SessionActParams::XStreamResponse } # @!method initialize(input:, frame_id: nil, options: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) - # @param input [String, Stagehand::Models::SessionActParams::Input::ActionInput] Natural language instruction or Action object + # @param input [String, Stagehand::Models::Action] Natural language instruction or Action object # # @param frame_id [String] Target frame ID for the action # @@ -72,47 +74,10 @@ module Input variant String # Action object returned by observe and used by act - variant -> { Stagehand::SessionActParams::Input::ActionInput } - - class ActionInput < Stagehand::Internal::Type::BaseModel - # @!attribute description - # Human-readable description of the action - # - # @return [String] - required :description, String - - # @!attribute selector - # CSS selector or XPath for the element - # - # @return [String] - required :selector, String - - # @!attribute arguments - # Arguments to pass to the method - # - # @return [Array, nil] - optional :arguments, Stagehand::Internal::Type::ArrayOf[String] - - # @!attribute method_ - # The method to execute (click, fill, etc.) - # - # @return [String, nil] - optional :method_, String, api_name: :method - - # @!method initialize(description:, selector:, arguments: nil, method_: nil) - # Action object returned by observe and used by act - # - # @param description [String] Human-readable description of the action - # - # @param selector [String] CSS selector or XPath for the element - # - # @param arguments [Array] Arguments to pass to the method - # - # @param method_ [String] The method to execute (click, fill, etc.) - end + variant -> { Stagehand::Action } # @!method self.variants - # @return [Array(String, Stagehand::Models::SessionActParams::Input::ActionInput)] + # @return [Array(String, Stagehand::Models::Action)] end class Options < Stagehand::Internal::Type::BaseModel diff --git a/lib/stagehand/models/session_act_response.rb b/lib/stagehand/models/session_act_response.rb index f0818da..20b07d5 100644 --- a/lib/stagehand/models/session_act_response.rb +++ b/lib/stagehand/models/session_act_response.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#act + # + # @see Stagehand::Resources::Sessions#act_streaming class SessionActResponse < Stagehand::Internal::Type::BaseModel # @!attribute data # diff --git a/lib/stagehand/models/session_execute_params.rb b/lib/stagehand/models/session_execute_params.rb index 7085878..5258c58 100644 --- a/lib/stagehand/models/session_execute_params.rb +++ b/lib/stagehand/models/session_execute_params.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#execute + # + # @see Stagehand::Resources::Sessions#execute_streaming class SessionExecuteParams < Stagehand::Internal::Type::BaseModel extend Stagehand::Internal::Type::RequestParameters::Converter include Stagehand::Internal::Type::RequestParameters diff --git a/lib/stagehand/models/session_execute_response.rb b/lib/stagehand/models/session_execute_response.rb index 9007483..661f8f9 100644 --- a/lib/stagehand/models/session_execute_response.rb +++ b/lib/stagehand/models/session_execute_response.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#execute + # + # @see Stagehand::Resources::Sessions#execute_streaming class SessionExecuteResponse < Stagehand::Internal::Type::BaseModel # @!attribute data # diff --git a/lib/stagehand/models/session_extract_params.rb b/lib/stagehand/models/session_extract_params.rb index ade43be..266d38b 100644 --- a/lib/stagehand/models/session_extract_params.rb +++ b/lib/stagehand/models/session_extract_params.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#extract + # + # @see Stagehand::Resources::Sessions#extract_streaming class SessionExtractParams < Stagehand::Internal::Type::BaseModel extend Stagehand::Internal::Type::RequestParameters::Converter include Stagehand::Internal::Type::RequestParameters diff --git a/lib/stagehand/models/session_extract_response.rb b/lib/stagehand/models/session_extract_response.rb index 12f1b28..fdf34d9 100644 --- a/lib/stagehand/models/session_extract_response.rb +++ b/lib/stagehand/models/session_extract_response.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#extract + # + # @see Stagehand::Resources::Sessions#extract_streaming class SessionExtractResponse < Stagehand::Internal::Type::BaseModel # @!attribute data # diff --git a/lib/stagehand/models/session_navigate_params.rb b/lib/stagehand/models/session_navigate_params.rb index 70682a6..61f56e8 100644 --- a/lib/stagehand/models/session_navigate_params.rb +++ b/lib/stagehand/models/session_navigate_params.rb @@ -24,6 +24,12 @@ class SessionNavigateParams < Stagehand::Internal::Type::BaseModel # @return [Stagehand::Models::SessionNavigateParams::Options, nil] optional :options, -> { Stagehand::SessionNavigateParams::Options } + # @!attribute stream_response + # Whether to stream the response via SSE + # + # @return [Boolean, nil] + optional :stream_response, Stagehand::Internal::Type::Boolean, api_name: :streamResponse + # @!attribute x_language # Client SDK language # @@ -48,13 +54,15 @@ class SessionNavigateParams < Stagehand::Internal::Type::BaseModel # @return [Symbol, Stagehand::Models::SessionNavigateParams::XStreamResponse, nil] optional :x_stream_response, enum: -> { Stagehand::SessionNavigateParams::XStreamResponse } - # @!method initialize(url:, frame_id: nil, options: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # @!method initialize(url:, frame_id: nil, options: nil, stream_response: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) # @param url [String] URL to navigate to # # @param frame_id [String] Target frame ID for the navigation # # @param options [Stagehand::Models::SessionNavigateParams::Options] # + # @param stream_response [Boolean] Whether to stream the response via SSE + # # @param x_language [Symbol, Stagehand::Models::SessionNavigateParams::XLanguage] Client SDK language # # @param x_sdk_version [String] Version of the Stagehand SDK diff --git a/lib/stagehand/models/session_observe_params.rb b/lib/stagehand/models/session_observe_params.rb index f4fd94e..54da902 100644 --- a/lib/stagehand/models/session_observe_params.rb +++ b/lib/stagehand/models/session_observe_params.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#observe + # + # @see Stagehand::Resources::Sessions#observe_streaming class SessionObserveParams < Stagehand::Internal::Type::BaseModel extend Stagehand::Internal::Type::RequestParameters::Converter include Stagehand::Internal::Type::RequestParameters diff --git a/lib/stagehand/models/session_observe_response.rb b/lib/stagehand/models/session_observe_response.rb index d483ca7..f9891b2 100644 --- a/lib/stagehand/models/session_observe_response.rb +++ b/lib/stagehand/models/session_observe_response.rb @@ -3,6 +3,8 @@ module Stagehand module Models # @see Stagehand::Resources::Sessions#observe + # + # @see Stagehand::Resources::Sessions#observe_streaming class SessionObserveResponse < Stagehand::Internal::Type::BaseModel # @!attribute data # diff --git a/lib/stagehand/models/session_start_response.rb b/lib/stagehand/models/session_start_response.rb index 3b91551..ff2e44d 100644 --- a/lib/stagehand/models/session_start_response.rb +++ b/lib/stagehand/models/session_start_response.rb @@ -27,16 +27,24 @@ class Data < Stagehand::Internal::Type::BaseModel # @return [Boolean] required :available, Stagehand::Internal::Type::Boolean + # @!attribute connect_url + # CDP WebSocket URL for connecting to the Browserbase cloud browser + # + # @return [String] + required :connect_url, String, api_name: :connectUrl + # @!attribute session_id - # Unique session identifier + # Unique Browserbase session identifier # # @return [String] required :session_id, String, api_name: :sessionId - # @!method initialize(available:, session_id:) + # @!method initialize(available:, connect_url:, session_id:) # @param available [Boolean] # - # @param session_id [String] Unique session identifier + # @param connect_url [String] CDP WebSocket URL for connecting to the Browserbase cloud browser + # + # @param session_id [String] Unique Browserbase session identifier end end end diff --git a/lib/stagehand/models/stream_event.rb b/lib/stagehand/models/stream_event.rb new file mode 100644 index 0000000..cb40861 --- /dev/null +++ b/lib/stagehand/models/stream_event.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module Stagehand + module Models + class StreamEvent < Stagehand::Internal::Type::BaseModel + # @!attribute id + # Unique identifier for this event + # + # @return [String] + required :id, String + + # @!attribute data + # + # @return [Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput, Stagehand::Models::StreamEvent::Data::StreamEventLogDataOutput] + required :data, union: -> { Stagehand::StreamEvent::Data } + + # @!attribute type + # Type of stream event - system events or log messages + # + # @return [Symbol, Stagehand::Models::StreamEvent::Type] + required :type, enum: -> { Stagehand::StreamEvent::Type } + + # @!method initialize(id:, data:, type:) + # Server-Sent Event emitted during streaming responses. Events are sent as + # `data: \n\n`. + # + # @param id [String] Unique identifier for this event + # + # @param data [Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput, Stagehand::Models::StreamEvent::Data::StreamEventLogDataOutput] + # + # @param type [Symbol, Stagehand::Models::StreamEvent::Type] Type of stream event - system events or log messages + + # @see Stagehand::Models::StreamEvent#data + module Data + extend Stagehand::Internal::Type::Union + + variant -> { Stagehand::StreamEvent::Data::StreamEventSystemDataOutput } + + variant -> { Stagehand::StreamEvent::Data::StreamEventLogDataOutput } + + class StreamEventSystemDataOutput < Stagehand::Internal::Type::BaseModel + # @!attribute status + # Current status of the streaming operation + # + # @return [Symbol, Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::Status] + required :status, enum: -> { Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status } + + # @!attribute error + # Error message (present when status is 'error') + # + # @return [String, nil] + optional :error, String + + # @!attribute result + # Operation result (present when status is 'finished') + # + # @return [Object, nil] + optional :result, Stagehand::Internal::Type::Unknown + + # @!method initialize(status:, error: nil, result: nil) + # @param status [Symbol, Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::Status] Current status of the streaming operation + # + # @param error [String] Error message (present when status is 'error') + # + # @param result [Object] Operation result (present when status is 'finished') + + # Current status of the streaming operation + # + # @see Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput#status + module Status + extend Stagehand::Internal::Type::Enum + + STARTING = :starting + CONNECTED = :connected + RUNNING = :running + FINISHED = :finished + ERROR = :error + + # @!method self.values + # @return [Array] + end + end + + class StreamEventLogDataOutput < Stagehand::Internal::Type::BaseModel + # @!attribute message + # Log message from the operation + # + # @return [String] + required :message, String + + # @!attribute status + # + # @return [Symbol, :running] + required :status, const: :running + + # @!method initialize(message:, status: :running) + # @param message [String] Log message from the operation + # + # @param status [Symbol, :running] + end + + # @!method self.variants + # @return [Array(Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput, Stagehand::Models::StreamEvent::Data::StreamEventLogDataOutput)] + end + + # Type of stream event - system events or log messages + # + # @see Stagehand::Models::StreamEvent#type + module Type + extend Stagehand::Internal::Type::Enum + + SYSTEM = :system + LOG = :log + + # @!method self.values + # @return [Array] + end + end + end +end diff --git a/lib/stagehand/resources/sessions.rb b/lib/stagehand/resources/sessions.rb index 6f39537..b113a18 100644 --- a/lib/stagehand/resources/sessions.rb +++ b/lib/stagehand/resources/sessions.rb @@ -3,6 +3,8 @@ module Stagehand module Resources class Sessions + # See {Stagehand::Resources::Sessions#act_streaming} for streaming counterpart. + # # Executes a browser action using natural language instructions or a predefined # Action object. # @@ -10,7 +12,7 @@ class Sessions # # @param id [String] Path param: Unique session identifier # - # @param input [String, Stagehand::Models::SessionActParams::Input::ActionInput] Body param: Natural language instruction or Action object + # @param input [String, Stagehand::Models::Action] Body param: Natural language instruction or Action object # # @param frame_id [String] Body param: Target frame ID for the action # @@ -31,6 +33,10 @@ class Sessions # @see Stagehand::Models::SessionActParams def act(id, params) parsed, options = Stagehand::SessionActParams.dump_request(params) + if parsed[:streamResponse] + message = "Please use `#act_streaming` for the streaming use case." + raise ArgumentError.new(message) + end header_params = { x_language: "x-language", @@ -48,6 +54,62 @@ def act(id, params) ) end + # See {Stagehand::Resources::Sessions#act} for non-streaming counterpart. + # + # Executes a browser action using natural language instructions or a predefined + # Action object. + # + # @overload act_streaming(id, input:, frame_id: nil, options: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # + # @param id [String] Path param: Unique session identifier + # + # @param input [String, Stagehand::Models::Action] Body param: Natural language instruction or Action object + # + # @param frame_id [String] Body param: Target frame ID for the action + # + # @param options [Stagehand::Models::SessionActParams::Options] Body param: + # + # @param x_language [Symbol, Stagehand::Models::SessionActParams::XLanguage] Header param: Client SDK language + # + # @param x_sdk_version [String] Header param: Version of the Stagehand SDK + # + # @param x_sent_at [Time] Header param: ISO timestamp when request was sent + # + # @param x_stream_response [Symbol, Stagehand::Models::SessionActParams::XStreamResponse] Header param: Whether to stream the response via SSE + # + # @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}, nil] + # + # @return [Stagehand::Internal::Stream] + # + # @see Stagehand::Models::SessionActParams + def act_streaming(id, params) + parsed, options = Stagehand::SessionActParams.dump_request(params) + unless parsed.fetch(:streamResponse, true) + message = "Please use `#act` for the non-streaming use case." + raise ArgumentError.new(message) + end + parsed.store(:streamResponse, true) + header_params = + { + x_language: "x-language", + x_sdk_version: "x-sdk-version", + x_sent_at: "x-sent-at", + x_stream_response: "x-stream-response" + } + @client.request( + method: :post, + path: ["v1/sessions/%1$s/act", id], + headers: { + "accept" => "text/event-stream", + **parsed.slice(*header_params.keys) + }.transform_keys(header_params), + body: parsed.except(*header_params.keys), + stream: Stagehand::Internal::Stream, + model: Stagehand::StreamEvent, + options: options + ) + end + # Terminates the browser session and releases all associated resources. # # @overload end_(id, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) @@ -83,6 +145,9 @@ def end_(id, params = {}) ) end + # See {Stagehand::Resources::Sessions#execute_streaming} for streaming + # counterpart. + # # Runs an autonomous AI agent that can perform complex multi-step browser tasks. # # @overload execute(id, agent_config:, execute_options:, frame_id: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) @@ -110,6 +175,10 @@ def end_(id, params = {}) # @see Stagehand::Models::SessionExecuteParams def execute(id, params) parsed, options = Stagehand::SessionExecuteParams.dump_request(params) + if parsed[:streamResponse] + message = "Please use `#execute_streaming` for the streaming use case." + raise ArgumentError.new(message) + end header_params = { x_language: "x-language", @@ -127,6 +196,64 @@ def execute(id, params) ) end + # See {Stagehand::Resources::Sessions#execute} for non-streaming counterpart. + # + # Runs an autonomous AI agent that can perform complex multi-step browser tasks. + # + # @overload execute_streaming(id, agent_config:, execute_options:, frame_id: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # + # @param id [String] Path param: Unique session identifier + # + # @param agent_config [Stagehand::Models::SessionExecuteParams::AgentConfig] Body param: + # + # @param execute_options [Stagehand::Models::SessionExecuteParams::ExecuteOptions] Body param: + # + # @param frame_id [String] Body param: Target frame ID for the agent + # + # @param x_language [Symbol, Stagehand::Models::SessionExecuteParams::XLanguage] Header param: Client SDK language + # + # @param x_sdk_version [String] Header param: Version of the Stagehand SDK + # + # @param x_sent_at [Time] Header param: ISO timestamp when request was sent + # + # @param x_stream_response [Symbol, Stagehand::Models::SessionExecuteParams::XStreamResponse] Header param: Whether to stream the response via SSE + # + # @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}, nil] + # + # @return [Stagehand::Internal::Stream] + # + # @see Stagehand::Models::SessionExecuteParams + def execute_streaming(id, params) + parsed, options = Stagehand::SessionExecuteParams.dump_request(params) + unless parsed.fetch(:streamResponse, true) + message = "Please use `#execute` for the non-streaming use case." + raise ArgumentError.new(message) + end + parsed.store(:streamResponse, true) + header_params = + { + x_language: "x-language", + x_sdk_version: "x-sdk-version", + x_sent_at: "x-sent-at", + x_stream_response: "x-stream-response" + } + @client.request( + method: :post, + path: ["v1/sessions/%1$s/agentExecute", id], + headers: { + "accept" => "text/event-stream", + **parsed.slice(*header_params.keys) + }.transform_keys(header_params), + body: parsed.except(*header_params.keys), + stream: Stagehand::Internal::Stream, + model: Stagehand::StreamEvent, + options: options + ) + end + + # See {Stagehand::Resources::Sessions#extract_streaming} for streaming + # counterpart. + # # Extracts structured data from the current page using AI-powered analysis. # # @overload extract(id, frame_id: nil, instruction: nil, options: nil, schema: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) @@ -156,6 +283,10 @@ def execute(id, params) # @see Stagehand::Models::SessionExtractParams def extract(id, params = {}) parsed, options = Stagehand::SessionExtractParams.dump_request(params) + if parsed[:streamResponse] + message = "Please use `#extract_streaming` for the streaming use case." + raise ArgumentError.new(message) + end header_params = { x_language: "x-language", @@ -173,9 +304,66 @@ def extract(id, params = {}) ) end + # See {Stagehand::Resources::Sessions#extract} for non-streaming counterpart. + # + # Extracts structured data from the current page using AI-powered analysis. + # + # @overload extract_streaming(id, frame_id: nil, instruction: nil, options: nil, schema: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # + # @param id [String] Path param: Unique session identifier + # + # @param frame_id [String] Body param: Target frame ID for the extraction + # + # @param instruction [String] Body param: Natural language instruction for what to extract + # + # @param options [Stagehand::Models::SessionExtractParams::Options] Body param: + # + # @param schema [Hash{Symbol=>Object}] Body param: JSON Schema defining the structure of data to extract + # + # @param x_language [Symbol, Stagehand::Models::SessionExtractParams::XLanguage] Header param: Client SDK language + # + # @param x_sdk_version [String] Header param: Version of the Stagehand SDK + # + # @param x_sent_at [Time] Header param: ISO timestamp when request was sent + # + # @param x_stream_response [Symbol, Stagehand::Models::SessionExtractParams::XStreamResponse] Header param: Whether to stream the response via SSE + # + # @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}, nil] + # + # @return [Stagehand::Internal::Stream] + # + # @see Stagehand::Models::SessionExtractParams + def extract_streaming(id, params = {}) + parsed, options = Stagehand::SessionExtractParams.dump_request(params) + unless parsed.fetch(:streamResponse, true) + message = "Please use `#extract` for the non-streaming use case." + raise ArgumentError.new(message) + end + parsed.store(:streamResponse, true) + header_params = + { + x_language: "x-language", + x_sdk_version: "x-sdk-version", + x_sent_at: "x-sent-at", + x_stream_response: "x-stream-response" + } + @client.request( + method: :post, + path: ["v1/sessions/%1$s/extract", id], + headers: { + "accept" => "text/event-stream", + **parsed.slice(*header_params.keys) + }.transform_keys(header_params), + body: parsed.except(*header_params.keys), + stream: Stagehand::Internal::Stream, + model: Stagehand::StreamEvent, + options: options + ) + end + # Navigates the browser to the specified URL. # - # @overload navigate(id, url:, frame_id: nil, options: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # @overload navigate(id, url:, frame_id: nil, options: nil, stream_response: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) # # @param id [String] Path param: Unique session identifier # @@ -185,6 +373,8 @@ def extract(id, params = {}) # # @param options [Stagehand::Models::SessionNavigateParams::Options] Body param: # + # @param stream_response [Boolean] Body param: Whether to stream the response via SSE + # # @param x_language [Symbol, Stagehand::Models::SessionNavigateParams::XLanguage] Header param: Client SDK language # # @param x_sdk_version [String] Header param: Version of the Stagehand SDK @@ -217,6 +407,9 @@ def navigate(id, params) ) end + # See {Stagehand::Resources::Sessions#observe_streaming} for streaming + # counterpart. + # # Identifies and returns available actions on the current page that match the # given instruction. # @@ -245,6 +438,10 @@ def navigate(id, params) # @see Stagehand::Models::SessionObserveParams def observe(id, params = {}) parsed, options = Stagehand::SessionObserveParams.dump_request(params) + if parsed[:streamResponse] + message = "Please use `#observe_streaming` for the streaming use case." + raise ArgumentError.new(message) + end header_params = { x_language: "x-language", @@ -262,6 +459,62 @@ def observe(id, params = {}) ) end + # See {Stagehand::Resources::Sessions#observe} for non-streaming counterpart. + # + # Identifies and returns available actions on the current page that match the + # given instruction. + # + # @overload observe_streaming(id, frame_id: nil, instruction: nil, options: nil, x_language: nil, x_sdk_version: nil, x_sent_at: nil, x_stream_response: nil, request_options: {}) + # + # @param id [String] Path param: Unique session identifier + # + # @param frame_id [String] Body param: Target frame ID for the observation + # + # @param instruction [String] Body param: Natural language instruction for what actions to find + # + # @param options [Stagehand::Models::SessionObserveParams::Options] Body param: + # + # @param x_language [Symbol, Stagehand::Models::SessionObserveParams::XLanguage] Header param: Client SDK language + # + # @param x_sdk_version [String] Header param: Version of the Stagehand SDK + # + # @param x_sent_at [Time] Header param: ISO timestamp when request was sent + # + # @param x_stream_response [Symbol, Stagehand::Models::SessionObserveParams::XStreamResponse] Header param: Whether to stream the response via SSE + # + # @param request_options [Stagehand::RequestOptions, Hash{Symbol=>Object}, nil] + # + # @return [Stagehand::Internal::Stream] + # + # @see Stagehand::Models::SessionObserveParams + def observe_streaming(id, params = {}) + parsed, options = Stagehand::SessionObserveParams.dump_request(params) + unless parsed.fetch(:streamResponse, true) + message = "Please use `#observe` for the non-streaming use case." + raise ArgumentError.new(message) + end + parsed.store(:streamResponse, true) + header_params = + { + x_language: "x-language", + x_sdk_version: "x-sdk-version", + x_sent_at: "x-sent-at", + x_stream_response: "x-stream-response" + } + @client.request( + method: :post, + path: ["v1/sessions/%1$s/observe", id], + headers: { + "accept" => "text/event-stream", + **parsed.slice(*header_params.keys) + }.transform_keys(header_params), + body: parsed.except(*header_params.keys), + stream: Stagehand::Internal::Stream, + model: Stagehand::StreamEvent, + options: options + ) + end + # Creates a new browser session with the specified configuration. Returns a # session ID used for all subsequent operations. # diff --git a/lib/stagehand/version.rb b/lib/stagehand/version.rb index 39fd331..f55cf9c 100644 --- a/lib/stagehand/version.rb +++ b/lib/stagehand/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Stagehand - VERSION = "0.3.0" + VERSION = "0.4.0" end diff --git a/rbi/stagehand/internal/stream.rbi b/rbi/stagehand/internal/stream.rbi new file mode 100644 index 0000000..894e1cb --- /dev/null +++ b/rbi/stagehand/internal/stream.rbi @@ -0,0 +1,20 @@ +# typed: strong + +module Stagehand + module Internal + class Stream + Message = + type_member(:in) do + { fixed: Stagehand::Internal::Util::ServerSentEvent } + end + Elem = type_member(:out) + + include Stagehand::Internal::Type::BaseStream + + # @api private + sig { override.returns(T::Enumerable[Elem]) } + private def iterator + end + end + end +end diff --git a/rbi/stagehand/internal/transport/base_client.rbi b/rbi/stagehand/internal/transport/base_client.rbi index d2e81a6..33ef53c 100644 --- a/rbi/stagehand/internal/transport/base_client.rbi +++ b/rbi/stagehand/internal/transport/base_client.rbi @@ -49,7 +49,15 @@ module Stagehand ] ] ), - stream: T.nilable(T::Class[T.anything]), + stream: + T.nilable( + T::Class[ + Stagehand::Internal::Type::BaseStream[ + T.anything, + Stagehand::Internal::Type::BaseModel + ] + ] + ), model: T.nilable(Stagehand::Internal::Type::Converter::Input), options: T.nilable(Stagehand::RequestOptions::OrHash) } @@ -269,7 +277,15 @@ module Stagehand ] ] ), - stream: T.nilable(T::Class[T.anything]), + stream: + T.nilable( + T::Class[ + Stagehand::Internal::Type::BaseStream[ + T.anything, + Stagehand::Internal::Type::BaseModel + ] + ] + ), model: T.nilable(Stagehand::Internal::Type::Converter::Input), options: T.nilable(Stagehand::RequestOptions::OrHash) ).returns(T.anything) diff --git a/rbi/stagehand/internal/type/base_stream.rbi b/rbi/stagehand/internal/type/base_stream.rbi new file mode 100644 index 0000000..01c3a66 --- /dev/null +++ b/rbi/stagehand/internal/type/base_stream.rbi @@ -0,0 +1,75 @@ +# typed: strong + +module Stagehand + module Internal + module Type + # @api private + # + # This module provides a base implementation for streaming responses in the SDK. + module BaseStream + include Enumerable + + Message = type_member(:in) + Elem = type_member(:out) + + sig { returns(Integer) } + attr_reader :status + + sig { returns(T::Hash[String, String]) } + attr_reader :headers + + sig { void } + def close + end + + # @api private + sig { overridable.returns(T::Enumerable[Elem]) } + private def iterator + end + + sig { params(blk: T.proc.params(arg0: Elem).void).void } + def each(&blk) + end + + sig { returns(T::Enumerator[Elem]) } + def to_enum + end + + # @api private + sig do + params( + model: + T.any(T::Class[T.anything], Stagehand::Internal::Type::Converter), + url: URI::Generic, + status: Integer, + headers: T::Hash[String, String], + response: Net::HTTPResponse, + unwrap: + T.any( + Symbol, + Integer, + T::Array[T.any(Symbol, Integer)], + T.proc.params(arg0: T.anything).returns(T.anything) + ), + stream: T::Enumerable[Message] + ).void + end + def initialize( + model:, + url:, + status:, + headers:, + response:, + unwrap:, + stream: + ) + end + + # @api private + sig { returns(String) } + def inspect + end + end + end + end +end diff --git a/rbi/stagehand/models.rbi b/rbi/stagehand/models.rbi index 5feb88c..d0bf01d 100644 --- a/rbi/stagehand/models.rbi +++ b/rbi/stagehand/models.rbi @@ -18,4 +18,6 @@ module Stagehand SessionObserveParams = Stagehand::Models::SessionObserveParams SessionStartParams = Stagehand::Models::SessionStartParams + + StreamEvent = Stagehand::Models::StreamEvent end diff --git a/rbi/stagehand/models/session_act_params.rbi b/rbi/stagehand/models/session_act_params.rbi index 7694a9e..dca0a54 100644 --- a/rbi/stagehand/models/session_act_params.rbi +++ b/rbi/stagehand/models/session_act_params.rbi @@ -12,9 +12,7 @@ module Stagehand end # Natural language instruction or Action object - sig do - returns(T.any(String, Stagehand::SessionActParams::Input::ActionInput)) - end + sig { returns(T.any(String, Stagehand::Action)) } attr_accessor :input # Target frame ID for the action @@ -75,11 +73,7 @@ module Stagehand sig do params( - input: - T.any( - String, - Stagehand::SessionActParams::Input::ActionInput::OrHash - ), + input: T.any(String, Stagehand::Action::OrHash), frame_id: String, options: Stagehand::SessionActParams::Options::OrHash, x_language: Stagehand::SessionActParams::XLanguage::OrSymbol, @@ -111,8 +105,7 @@ module Stagehand sig do override.returns( { - input: - T.any(String, Stagehand::SessionActParams::Input::ActionInput), + input: T.any(String, Stagehand::Action), frame_id: String, options: Stagehand::SessionActParams::Options, x_language: Stagehand::SessionActParams::XLanguage::OrSymbol, @@ -131,76 +124,7 @@ module Stagehand module Input extend Stagehand::Internal::Type::Union - Variants = - T.type_alias do - T.any(String, Stagehand::SessionActParams::Input::ActionInput) - end - - class ActionInput < Stagehand::Internal::Type::BaseModel - OrHash = - T.type_alias do - T.any( - Stagehand::SessionActParams::Input::ActionInput, - Stagehand::Internal::AnyHash - ) - end - - # Human-readable description of the action - sig { returns(String) } - attr_accessor :description - - # CSS selector or XPath for the element - sig { returns(String) } - attr_accessor :selector - - # Arguments to pass to the method - sig { returns(T.nilable(T::Array[String])) } - attr_reader :arguments - - sig { params(arguments: T::Array[String]).void } - attr_writer :arguments - - # The method to execute (click, fill, etc.) - sig { returns(T.nilable(String)) } - attr_reader :method_ - - sig { params(method_: String).void } - attr_writer :method_ - - # Action object returned by observe and used by act - sig do - params( - description: String, - selector: String, - arguments: T::Array[String], - method_: String - ).returns(T.attached_class) - end - def self.new( - # Human-readable description of the action - description:, - # CSS selector or XPath for the element - selector:, - # Arguments to pass to the method - arguments: nil, - # The method to execute (click, fill, etc.) - method_: nil - ) - end - - sig do - override.returns( - { - description: String, - selector: String, - arguments: T::Array[String], - method_: String - } - ) - end - def to_hash - end - end + Variants = T.type_alias { T.any(String, Stagehand::Action) } sig do override.returns( diff --git a/rbi/stagehand/models/session_navigate_params.rbi b/rbi/stagehand/models/session_navigate_params.rbi index 25d06cd..d696cdf 100644 --- a/rbi/stagehand/models/session_navigate_params.rbi +++ b/rbi/stagehand/models/session_navigate_params.rbi @@ -30,6 +30,13 @@ module Stagehand end attr_writer :options + # Whether to stream the response via SSE + sig { returns(T.nilable(T::Boolean)) } + attr_reader :stream_response + + sig { params(stream_response: T::Boolean).void } + attr_writer :stream_response + # Client SDK language sig do returns( @@ -80,6 +87,7 @@ module Stagehand url: String, frame_id: String, options: Stagehand::SessionNavigateParams::Options::OrHash, + stream_response: T::Boolean, x_language: Stagehand::SessionNavigateParams::XLanguage::OrSymbol, x_sdk_version: String, x_sent_at: Time, @@ -94,6 +102,8 @@ module Stagehand # Target frame ID for the navigation frame_id: nil, options: nil, + # Whether to stream the response via SSE + stream_response: nil, # Client SDK language x_language: nil, # Version of the Stagehand SDK @@ -112,6 +122,7 @@ module Stagehand url: String, frame_id: String, options: Stagehand::SessionNavigateParams::Options, + stream_response: T::Boolean, x_language: Stagehand::SessionNavigateParams::XLanguage::OrSymbol, x_sdk_version: String, x_sent_at: Time, diff --git a/rbi/stagehand/models/session_start_response.rbi b/rbi/stagehand/models/session_start_response.rbi index e227e92..6ca9940 100644 --- a/rbi/stagehand/models/session_start_response.rbi +++ b/rbi/stagehand/models/session_start_response.rbi @@ -59,23 +59,35 @@ module Stagehand sig { returns(T::Boolean) } attr_accessor :available - # Unique session identifier + # CDP WebSocket URL for connecting to the Browserbase cloud browser + sig { returns(String) } + attr_accessor :connect_url + + # Unique Browserbase session identifier sig { returns(String) } attr_accessor :session_id sig do - params(available: T::Boolean, session_id: String).returns( - T.attached_class - ) + params( + available: T::Boolean, + connect_url: String, + session_id: String + ).returns(T.attached_class) end def self.new( available:, - # Unique session identifier + # CDP WebSocket URL for connecting to the Browserbase cloud browser + connect_url:, + # Unique Browserbase session identifier session_id: ) end - sig { override.returns({ available: T::Boolean, session_id: String }) } + sig do + override.returns( + { available: T::Boolean, connect_url: String, session_id: String } + ) + end def to_hash end end diff --git a/rbi/stagehand/models/stream_event.rbi b/rbi/stagehand/models/stream_event.rbi new file mode 100644 index 0000000..77b90da --- /dev/null +++ b/rbi/stagehand/models/stream_event.rbi @@ -0,0 +1,237 @@ +# typed: strong + +module Stagehand + module Models + class StreamEvent < Stagehand::Internal::Type::BaseModel + OrHash = + T.type_alias do + T.any(Stagehand::StreamEvent, Stagehand::Internal::AnyHash) + end + + # Unique identifier for this event + sig { returns(String) } + attr_accessor :id + + sig { returns(Stagehand::StreamEvent::Data::Variants) } + attr_accessor :data + + # Type of stream event - system events or log messages + sig { returns(Stagehand::StreamEvent::Type::TaggedSymbol) } + attr_accessor :type + + # Server-Sent Event emitted during streaming responses. Events are sent as + # `data: \n\n`. + sig do + params( + id: String, + data: + T.any( + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::OrHash, + Stagehand::StreamEvent::Data::StreamEventLogDataOutput::OrHash + ), + type: Stagehand::StreamEvent::Type::OrSymbol + ).returns(T.attached_class) + end + def self.new( + # Unique identifier for this event + id:, + data:, + # Type of stream event - system events or log messages + type: + ) + end + + sig do + override.returns( + { + id: String, + data: Stagehand::StreamEvent::Data::Variants, + type: Stagehand::StreamEvent::Type::TaggedSymbol + } + ) + end + def to_hash + end + + module Data + extend Stagehand::Internal::Type::Union + + Variants = + T.type_alias do + T.any( + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput, + Stagehand::StreamEvent::Data::StreamEventLogDataOutput + ) + end + + class StreamEventSystemDataOutput < Stagehand::Internal::Type::BaseModel + OrHash = + T.type_alias do + T.any( + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput, + Stagehand::Internal::AnyHash + ) + end + + # Current status of the streaming operation + sig do + returns( + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + end + attr_accessor :status + + # Error message (present when status is 'error') + sig { returns(T.nilable(String)) } + attr_reader :error + + sig { params(error: String).void } + attr_writer :error + + # Operation result (present when status is 'finished') + sig { returns(T.nilable(T.anything)) } + attr_reader :result + + sig { params(result: T.anything).void } + attr_writer :result + + sig do + params( + status: + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::OrSymbol, + error: String, + result: T.anything + ).returns(T.attached_class) + end + def self.new( + # Current status of the streaming operation + status:, + # Error message (present when status is 'error') + error: nil, + # Operation result (present when status is 'finished') + result: nil + ) + end + + sig do + override.returns( + { + status: + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol, + error: String, + result: T.anything + } + ) + end + def to_hash + end + + # Current status of the streaming operation + module Status + extend Stagehand::Internal::Type::Enum + + TaggedSymbol = + T.type_alias do + T.all( + Symbol, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status + ) + end + OrSymbol = T.type_alias { T.any(Symbol, String) } + + STARTING = + T.let( + :starting, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + CONNECTED = + T.let( + :connected, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + RUNNING = + T.let( + :running, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + FINISHED = + T.let( + :finished, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + ERROR = + T.let( + :error, + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ) + + sig do + override.returns( + T::Array[ + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput::Status::TaggedSymbol + ] + ) + end + def self.values + end + end + end + + class StreamEventLogDataOutput < Stagehand::Internal::Type::BaseModel + OrHash = + T.type_alias do + T.any( + Stagehand::StreamEvent::Data::StreamEventLogDataOutput, + Stagehand::Internal::AnyHash + ) + end + + # Log message from the operation + sig { returns(String) } + attr_accessor :message + + sig { returns(Symbol) } + attr_accessor :status + + sig do + params(message: String, status: Symbol).returns(T.attached_class) + end + def self.new( + # Log message from the operation + message:, + status: :running + ) + end + + sig { override.returns({ message: String, status: Symbol }) } + def to_hash + end + end + + sig do + override.returns(T::Array[Stagehand::StreamEvent::Data::Variants]) + end + def self.variants + end + end + + # Type of stream event - system events or log messages + module Type + extend Stagehand::Internal::Type::Enum + + TaggedSymbol = + T.type_alias { T.all(Symbol, Stagehand::StreamEvent::Type) } + OrSymbol = T.type_alias { T.any(Symbol, String) } + + SYSTEM = T.let(:system, Stagehand::StreamEvent::Type::TaggedSymbol) + LOG = T.let(:log, Stagehand::StreamEvent::Type::TaggedSymbol) + + sig do + override.returns(T::Array[Stagehand::StreamEvent::Type::TaggedSymbol]) + end + def self.values + end + end + end + end +end diff --git a/rbi/stagehand/resources/sessions.rbi b/rbi/stagehand/resources/sessions.rbi index 682dd7e..ad3b978 100644 --- a/rbi/stagehand/resources/sessions.rbi +++ b/rbi/stagehand/resources/sessions.rbi @@ -3,16 +3,14 @@ module Stagehand module Resources class Sessions + # See {Stagehand::Resources::Sessions#act_streaming} for streaming counterpart. + # # Executes a browser action using natural language instructions or a predefined # Action object. sig do params( id: String, - input: - T.any( - String, - Stagehand::SessionActParams::Input::ActionInput::OrHash - ), + input: T.any(String, Stagehand::Action::OrHash), frame_id: String, options: Stagehand::SessionActParams::Options::OrHash, x_language: Stagehand::SessionActParams::XLanguage::OrSymbol, @@ -20,6 +18,7 @@ module Stagehand x_sent_at: Time, x_stream_response: Stagehand::SessionActParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, request_options: Stagehand::RequestOptions::OrHash ).returns(Stagehand::Models::SessionActResponse) end @@ -40,6 +39,52 @@ module Stagehand x_sent_at: nil, # Header param: Whether to stream the response via SSE x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use `#act_streaming` or + # `#act` for streaming and non-streaming use cases, respectively. + stream_response: false, + request_options: {} + ) + end + + # See {Stagehand::Resources::Sessions#act} for non-streaming counterpart. + # + # Executes a browser action using natural language instructions or a predefined + # Action object. + sig do + params( + id: String, + input: T.any(String, Stagehand::Action::OrHash), + frame_id: String, + options: Stagehand::SessionActParams::Options::OrHash, + x_language: Stagehand::SessionActParams::XLanguage::OrSymbol, + x_sdk_version: String, + x_sent_at: Time, + x_stream_response: + Stagehand::SessionActParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, + request_options: Stagehand::RequestOptions::OrHash + ).returns(Stagehand::Internal::Stream[Stagehand::StreamEvent]) + end + def act_streaming( + # Path param: Unique session identifier + id, + # Body param: Natural language instruction or Action object + input:, + # Body param: Target frame ID for the action + frame_id: nil, + # Body param: + options: nil, + # Header param: Client SDK language + x_language: nil, + # Header param: Version of the Stagehand SDK + x_sdk_version: nil, + # Header param: ISO timestamp when request was sent + x_sent_at: nil, + # Header param: Whether to stream the response via SSE + x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use `#act_streaming` or + # `#act` for streaming and non-streaming use cases, respectively. + stream_response: true, request_options: {} ) end @@ -71,6 +116,9 @@ module Stagehand ) end + # See {Stagehand::Resources::Sessions#execute_streaming} for streaming + # counterpart. + # # Runs an autonomous AI agent that can perform complex multi-step browser tasks. sig do params( @@ -84,6 +132,7 @@ module Stagehand x_sent_at: Time, x_stream_response: Stagehand::SessionExecuteParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, request_options: Stagehand::RequestOptions::OrHash ).returns(Stagehand::Models::SessionExecuteResponse) end @@ -104,10 +153,61 @@ module Stagehand x_sent_at: nil, # Header param: Whether to stream the response via SSE x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#execute_streaming` or `#execute` for streaming and non-streaming use cases, + # respectively. + stream_response: false, request_options: {} ) end + # See {Stagehand::Resources::Sessions#execute} for non-streaming counterpart. + # + # Runs an autonomous AI agent that can perform complex multi-step browser tasks. + sig do + params( + id: String, + agent_config: Stagehand::SessionExecuteParams::AgentConfig::OrHash, + execute_options: + Stagehand::SessionExecuteParams::ExecuteOptions::OrHash, + frame_id: String, + x_language: Stagehand::SessionExecuteParams::XLanguage::OrSymbol, + x_sdk_version: String, + x_sent_at: Time, + x_stream_response: + Stagehand::SessionExecuteParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, + request_options: Stagehand::RequestOptions::OrHash + ).returns(Stagehand::Internal::Stream[Stagehand::StreamEvent]) + end + def execute_streaming( + # Path param: Unique session identifier + id, + # Body param: + agent_config:, + # Body param: + execute_options:, + # Body param: Target frame ID for the agent + frame_id: nil, + # Header param: Client SDK language + x_language: nil, + # Header param: Version of the Stagehand SDK + x_sdk_version: nil, + # Header param: ISO timestamp when request was sent + x_sent_at: nil, + # Header param: Whether to stream the response via SSE + x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#execute_streaming` or `#execute` for streaming and non-streaming use cases, + # respectively. + stream_response: true, + request_options: {} + ) + end + + # See {Stagehand::Resources::Sessions#extract_streaming} for streaming + # counterpart. + # # Extracts structured data from the current page using AI-powered analysis. sig do params( @@ -121,6 +221,7 @@ module Stagehand x_sent_at: Time, x_stream_response: Stagehand::SessionExtractParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, request_options: Stagehand::RequestOptions::OrHash ).returns(Stagehand::Models::SessionExtractResponse) end @@ -143,6 +244,56 @@ module Stagehand x_sent_at: nil, # Header param: Whether to stream the response via SSE x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#extract_streaming` or `#extract` for streaming and non-streaming use cases, + # respectively. + stream_response: false, + request_options: {} + ) + end + + # See {Stagehand::Resources::Sessions#extract} for non-streaming counterpart. + # + # Extracts structured data from the current page using AI-powered analysis. + sig do + params( + id: String, + frame_id: String, + instruction: String, + options: Stagehand::SessionExtractParams::Options::OrHash, + schema: T::Hash[Symbol, T.anything], + x_language: Stagehand::SessionExtractParams::XLanguage::OrSymbol, + x_sdk_version: String, + x_sent_at: Time, + x_stream_response: + Stagehand::SessionExtractParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, + request_options: Stagehand::RequestOptions::OrHash + ).returns(Stagehand::Internal::Stream[Stagehand::StreamEvent]) + end + def extract_streaming( + # Path param: Unique session identifier + id, + # Body param: Target frame ID for the extraction + frame_id: nil, + # Body param: Natural language instruction for what to extract + instruction: nil, + # Body param: + options: nil, + # Body param: JSON Schema defining the structure of data to extract + schema: nil, + # Header param: Client SDK language + x_language: nil, + # Header param: Version of the Stagehand SDK + x_sdk_version: nil, + # Header param: ISO timestamp when request was sent + x_sent_at: nil, + # Header param: Whether to stream the response via SSE + x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#extract_streaming` or `#extract` for streaming and non-streaming use cases, + # respectively. + stream_response: true, request_options: {} ) end @@ -154,6 +305,7 @@ module Stagehand url: String, frame_id: String, options: Stagehand::SessionNavigateParams::Options::OrHash, + stream_response: T::Boolean, x_language: Stagehand::SessionNavigateParams::XLanguage::OrSymbol, x_sdk_version: String, x_sent_at: Time, @@ -171,6 +323,8 @@ module Stagehand frame_id: nil, # Body param: options: nil, + # Body param: Whether to stream the response via SSE + stream_response: nil, # Header param: Client SDK language x_language: nil, # Header param: Version of the Stagehand SDK @@ -183,6 +337,9 @@ module Stagehand ) end + # See {Stagehand::Resources::Sessions#observe_streaming} for streaming + # counterpart. + # # Identifies and returns available actions on the current page that match the # given instruction. sig do @@ -196,6 +353,7 @@ module Stagehand x_sent_at: Time, x_stream_response: Stagehand::SessionObserveParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, request_options: Stagehand::RequestOptions::OrHash ).returns(Stagehand::Models::SessionObserveResponse) end @@ -216,6 +374,54 @@ module Stagehand x_sent_at: nil, # Header param: Whether to stream the response via SSE x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#observe_streaming` or `#observe` for streaming and non-streaming use cases, + # respectively. + stream_response: false, + request_options: {} + ) + end + + # See {Stagehand::Resources::Sessions#observe} for non-streaming counterpart. + # + # Identifies and returns available actions on the current page that match the + # given instruction. + sig do + params( + id: String, + frame_id: String, + instruction: String, + options: Stagehand::SessionObserveParams::Options::OrHash, + x_language: Stagehand::SessionObserveParams::XLanguage::OrSymbol, + x_sdk_version: String, + x_sent_at: Time, + x_stream_response: + Stagehand::SessionObserveParams::XStreamResponse::OrSymbol, + stream_response: T.noreturn, + request_options: Stagehand::RequestOptions::OrHash + ).returns(Stagehand::Internal::Stream[Stagehand::StreamEvent]) + end + def observe_streaming( + # Path param: Unique session identifier + id, + # Body param: Target frame ID for the observation + frame_id: nil, + # Body param: Natural language instruction for what actions to find + instruction: nil, + # Body param: + options: nil, + # Header param: Client SDK language + x_language: nil, + # Header param: Version of the Stagehand SDK + x_sdk_version: nil, + # Header param: ISO timestamp when request was sent + x_sent_at: nil, + # Header param: Whether to stream the response via SSE + x_stream_response: nil, + # There is no need to provide `stream_response:`. Instead, use + # `#observe_streaming` or `#observe` for streaming and non-streaming use cases, + # respectively. + stream_response: true, request_options: {} ) end diff --git a/sig/stagehand/internal/stream.rbs b/sig/stagehand/internal/stream.rbs new file mode 100644 index 0000000..17119f0 --- /dev/null +++ b/sig/stagehand/internal/stream.rbs @@ -0,0 +1,9 @@ +module Stagehand + module Internal + class Stream[Elem] + include Stagehand::Internal::Type::BaseStream[Stagehand::Internal::Util::server_sent_event, Elem] + + private def iterator: -> Enumerable[Elem] + end + end +end diff --git a/sig/stagehand/internal/type/base_stream.rbs b/sig/stagehand/internal/type/base_stream.rbs new file mode 100644 index 0000000..ab7e2e8 --- /dev/null +++ b/sig/stagehand/internal/type/base_stream.rbs @@ -0,0 +1,38 @@ +module Stagehand + module Internal + module Type + module BaseStream[Message, Elem] + include Enumerable[Elem] + + attr_reader status: Integer + + attr_reader headers: ::Hash[String, String] + + def close: -> void + + private def iterator: -> Enumerable[Elem] + + def each: { (Elem arg0) -> void } -> void + + def to_enum: -> Enumerator[Elem] + + alias enum_for to_enum + + def initialize: ( + model: Class | Stagehand::Internal::Type::Converter, + url: URI::Generic, + status: Integer, + headers: ::Hash[String, String], + response: top, + unwrap: Symbol + | Integer + | ::Array[Symbol | Integer] + | ^(top arg0) -> top, + stream: Enumerable[Message] + ) -> void + + def inspect: -> String + end + end + end +end diff --git a/sig/stagehand/models.rbs b/sig/stagehand/models.rbs index 3262ef0..ebfaad6 100644 --- a/sig/stagehand/models.rbs +++ b/sig/stagehand/models.rbs @@ -16,4 +16,6 @@ module Stagehand class SessionObserveParams = Stagehand::Models::SessionObserveParams class SessionStartParams = Stagehand::Models::SessionStartParams + + class StreamEvent = Stagehand::Models::StreamEvent end diff --git a/sig/stagehand/models/session_act_params.rbs b/sig/stagehand/models/session_act_params.rbs index 5152874..650a3cc 100644 --- a/sig/stagehand/models/session_act_params.rbs +++ b/sig/stagehand/models/session_act_params.rbs @@ -70,47 +70,11 @@ module Stagehand request_options: Stagehand::RequestOptions } - type input = String | Stagehand::SessionActParams::Input::ActionInput + type input = String | Stagehand::Action module Input extend Stagehand::Internal::Type::Union - type action_input = - { - description: String, - selector: String, - arguments: ::Array[String], - method_: String - } - - class ActionInput < Stagehand::Internal::Type::BaseModel - attr_accessor description: String - - attr_accessor selector: String - - attr_reader arguments: ::Array[String]? - - def arguments=: (::Array[String]) -> ::Array[String] - - attr_reader method_: String? - - def method_=: (String) -> String - - def initialize: ( - description: String, - selector: String, - ?arguments: ::Array[String], - ?method_: String - ) -> void - - def to_hash: -> { - description: String, - selector: String, - arguments: ::Array[String], - method_: String - } - end - def self?.variants: -> ::Array[Stagehand::Models::SessionActParams::input] end diff --git a/sig/stagehand/models/session_navigate_params.rbs b/sig/stagehand/models/session_navigate_params.rbs index fb28cef..ced7f1f 100644 --- a/sig/stagehand/models/session_navigate_params.rbs +++ b/sig/stagehand/models/session_navigate_params.rbs @@ -5,6 +5,7 @@ module Stagehand url: String, frame_id: String, options: Stagehand::SessionNavigateParams::Options, + stream_response: bool, x_language: Stagehand::Models::SessionNavigateParams::x_language, x_sdk_version: String, x_sent_at: Time, @@ -28,6 +29,10 @@ module Stagehand Stagehand::SessionNavigateParams::Options ) -> Stagehand::SessionNavigateParams::Options + attr_reader stream_response: bool? + + def stream_response=: (bool) -> bool + attr_reader x_language: Stagehand::Models::SessionNavigateParams::x_language? def x_language=: ( @@ -52,6 +57,7 @@ module Stagehand url: String, ?frame_id: String, ?options: Stagehand::SessionNavigateParams::Options, + ?stream_response: bool, ?x_language: Stagehand::Models::SessionNavigateParams::x_language, ?x_sdk_version: String, ?x_sent_at: Time, @@ -63,6 +69,7 @@ module Stagehand url: String, frame_id: String, options: Stagehand::SessionNavigateParams::Options, + stream_response: bool, x_language: Stagehand::Models::SessionNavigateParams::x_language, x_sdk_version: String, x_sent_at: Time, diff --git a/sig/stagehand/models/session_start_response.rbs b/sig/stagehand/models/session_start_response.rbs index f772cea..6e03a62 100644 --- a/sig/stagehand/models/session_start_response.rbs +++ b/sig/stagehand/models/session_start_response.rbs @@ -18,16 +18,26 @@ module Stagehand success: bool } - type data = { available: bool, session_id: String } + type data = { available: bool, connect_url: String, session_id: String } class Data < Stagehand::Internal::Type::BaseModel attr_accessor available: bool - attr_accessor session_id: String + attr_accessor connect_url: String - def initialize: (available: bool, session_id: String) -> void + attr_accessor session_id: String - def to_hash: -> { available: bool, session_id: String } + def initialize: ( + available: bool, + connect_url: String, + session_id: String + ) -> void + + def to_hash: -> { + available: bool, + connect_url: String, + session_id: String + } end end end diff --git a/sig/stagehand/models/stream_event.rbs b/sig/stagehand/models/stream_event.rbs new file mode 100644 index 0000000..5821902 --- /dev/null +++ b/sig/stagehand/models/stream_event.rbs @@ -0,0 +1,109 @@ +module Stagehand + module Models + type stream_event = + { + id: String, + data: Stagehand::Models::StreamEvent::data, + type: Stagehand::Models::StreamEvent::type_ + } + + class StreamEvent < Stagehand::Internal::Type::BaseModel + attr_accessor id: String + + attr_accessor data: Stagehand::Models::StreamEvent::data + + attr_accessor type: Stagehand::Models::StreamEvent::type_ + + def initialize: ( + id: String, + data: Stagehand::Models::StreamEvent::data, + type: Stagehand::Models::StreamEvent::type_ + ) -> void + + def to_hash: -> { + id: String, + data: Stagehand::Models::StreamEvent::data, + type: Stagehand::Models::StreamEvent::type_ + } + + type data = + Stagehand::StreamEvent::Data::StreamEventSystemDataOutput + | Stagehand::StreamEvent::Data::StreamEventLogDataOutput + + module Data + extend Stagehand::Internal::Type::Union + + type stream_event_system_data_output = + { + status: Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::status, + error: String, + result: top + } + + class StreamEventSystemDataOutput < Stagehand::Internal::Type::BaseModel + attr_accessor status: Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::status + + attr_reader error: String? + + def error=: (String) -> String + + attr_reader result: top? + + def result=: (top) -> top + + def initialize: ( + status: Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::status, + ?error: String, + ?result: top + ) -> void + + def to_hash: -> { + status: Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::status, + error: String, + result: top + } + + type status = :starting | :connected | :running | :finished | :error + + module Status + extend Stagehand::Internal::Type::Enum + + STARTING: :starting + CONNECTED: :connected + RUNNING: :running + FINISHED: :finished + ERROR: :error + + def self?.values: -> ::Array[Stagehand::Models::StreamEvent::Data::StreamEventSystemDataOutput::status] + end + end + + type stream_event_log_data_output = + { message: String, status: :running } + + class StreamEventLogDataOutput < Stagehand::Internal::Type::BaseModel + attr_accessor message: String + + attr_accessor status: :running + + def initialize: (message: String, ?status: :running) -> void + + def to_hash: -> { message: String, status: :running } + end + + def self?.variants: -> ::Array[Stagehand::Models::StreamEvent::data] + end + + type type_ = :system | :log + + module Type + extend Stagehand::Internal::Type::Enum + + SYSTEM: :system + LOG: :log + + def self?.values: -> ::Array[Stagehand::Models::StreamEvent::type_] + end + end + end +end diff --git a/sig/stagehand/resources/sessions.rbs b/sig/stagehand/resources/sessions.rbs index 720c5f4..75ba1bb 100644 --- a/sig/stagehand/resources/sessions.rbs +++ b/sig/stagehand/resources/sessions.rbs @@ -13,6 +13,18 @@ module Stagehand ?request_options: Stagehand::request_opts ) -> Stagehand::Models::SessionActResponse + def act_streaming: ( + String id, + input: Stagehand::Models::SessionActParams::input, + ?frame_id: String, + ?options: Stagehand::SessionActParams::Options, + ?x_language: Stagehand::Models::SessionActParams::x_language, + ?x_sdk_version: String, + ?x_sent_at: Time, + ?x_stream_response: Stagehand::Models::SessionActParams::x_stream_response, + ?request_options: Stagehand::request_opts + ) -> Stagehand::Internal::Stream[Stagehand::StreamEvent] + def end_: ( String id, ?x_language: Stagehand::Models::SessionEndParams::x_language, @@ -34,6 +46,18 @@ module Stagehand ?request_options: Stagehand::request_opts ) -> Stagehand::Models::SessionExecuteResponse + def execute_streaming: ( + String id, + agent_config: Stagehand::SessionExecuteParams::AgentConfig, + execute_options: Stagehand::SessionExecuteParams::ExecuteOptions, + ?frame_id: String, + ?x_language: Stagehand::Models::SessionExecuteParams::x_language, + ?x_sdk_version: String, + ?x_sent_at: Time, + ?x_stream_response: Stagehand::Models::SessionExecuteParams::x_stream_response, + ?request_options: Stagehand::request_opts + ) -> Stagehand::Internal::Stream[Stagehand::StreamEvent] + def extract: ( String id, ?frame_id: String, @@ -47,11 +71,25 @@ module Stagehand ?request_options: Stagehand::request_opts ) -> Stagehand::Models::SessionExtractResponse + def extract_streaming: ( + String id, + ?frame_id: String, + ?instruction: String, + ?options: Stagehand::SessionExtractParams::Options, + ?schema: ::Hash[Symbol, top], + ?x_language: Stagehand::Models::SessionExtractParams::x_language, + ?x_sdk_version: String, + ?x_sent_at: Time, + ?x_stream_response: Stagehand::Models::SessionExtractParams::x_stream_response, + ?request_options: Stagehand::request_opts + ) -> Stagehand::Internal::Stream[Stagehand::StreamEvent] + def navigate: ( String id, url: String, ?frame_id: String, ?options: Stagehand::SessionNavigateParams::Options, + ?stream_response: bool, ?x_language: Stagehand::Models::SessionNavigateParams::x_language, ?x_sdk_version: String, ?x_sent_at: Time, @@ -71,6 +109,18 @@ module Stagehand ?request_options: Stagehand::request_opts ) -> Stagehand::Models::SessionObserveResponse + def observe_streaming: ( + String id, + ?frame_id: String, + ?instruction: String, + ?options: Stagehand::SessionObserveParams::Options, + ?x_language: Stagehand::Models::SessionObserveParams::x_language, + ?x_sdk_version: String, + ?x_sent_at: Time, + ?x_stream_response: Stagehand::Models::SessionObserveParams::x_stream_response, + ?request_options: Stagehand::request_opts + ) -> Stagehand::Internal::Stream[Stagehand::StreamEvent] + def start: ( model_name: String, ?act_timeout_ms: Float,