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 0c3ce73..d94df07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## 3.20.0 (2026-05-06) + +Full Changelog: [v3.19.3...v3.20.0](https://github.com/browserbase/stagehand-java/compare/v3.19.3...v3.20.0) + +### Features + +* [feat]: add `ignoreSelectors` to `extract()` ([ab25ab1](https://github.com/browserbase/stagehand-java/commit/ab25ab1c249cb689ea39cb2e3970927d616ddefd)) +* [STG-1798] feat: support Browserbase verified sessions ([59ee151](https://github.com/browserbase/stagehand-java/commit/59ee1516eb17d6b8474b12d2722ff4627fa2557c)) +* [STG-1808] Deprecate Browserbase project ID ([4700262](https://github.com/browserbase/stagehand-java/commit/47002629082ee0a602b2b4739be7b7b605337040)) +* Bedrock auth passthrough ([7d5037a](https://github.com/browserbase/stagehand-java/commit/7d5037abacedbee829f0fe238565ed81c211eba9)) +* **client:** more robust error parsing ([c7d063a](https://github.com/browserbase/stagehand-java/commit/c7d063aa513f6b62ca7e95d0031a5018516a114f)) +* **client:** support proxy authentication ([7572751](https://github.com/browserbase/stagehand-java/commit/7572751215d53839a93f8d0fc1dd303994813aca)) +* remove experimental requirement on agent variables ([#2079](https://github.com/browserbase/stagehand-java/issues/2079)) ([1a0a44c](https://github.com/browserbase/stagehand-java/commit/1a0a44c9d3d95f32df91a72ee3d1f42c7634aa6e)) +* Revert "[STG-1573] Add providerOptions for extensible model auth ([#1822](https://github.com/browserbase/stagehand-java/issues/1822))" ([c337e61](https://github.com/browserbase/stagehand-java/commit/c337e61dd9b2cc366bceec0e7119874acdc3b069)) +* support setting headers via env ([ab67768](https://github.com/browserbase/stagehand-java/commit/ab677681d664219c7f006c72b49b723922280a26)) + + +### Performance Improvements + +* **client:** create one json mapper ([5f27b6b](https://github.com/browserbase/stagehand-java/commit/5f27b6bdce009ea88970dea1ce52cd1d29169c98)) + + +### Chores + +* remove duplicated dokka setup ([25bb04d](https://github.com/browserbase/stagehand-java/commit/25bb04d02c26e2929adf6b2e1ea0d37b2843880f)) + + +### Documentation + +* clarify forwards compat behavior ([0463c65](https://github.com/browserbase/stagehand-java/commit/0463c65a44d0b165f9a6f850d0096cb6af207b97)) +* remove bad semicolon ([b3e9df1](https://github.com/browserbase/stagehand-java/commit/b3e9df1d14045e2a4ebc18bb16b75453856e6311)) + ## 3.19.3 (2026-04-03) Full Changelog: [v3.18.0...v3.19.3](https://github.com/browserbase/stagehand-java/compare/v3.18.0...v3.19.3) diff --git a/README.md b/README.md index 7179a61..86c046b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-java)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/3.19.3) -[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/3.19.3/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/3.19.3) +[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-java)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/3.20.0) +[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/3.20.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/3.20.0) @@ -85,7 +85,7 @@ Most existing browser automation tools either require you to write low-level cod ### Gradle ```kotlin -implementation("com.browserbase.api:stagehand-java:3.19.3") +implementation("com.browserbase.api:stagehand-java:3.20.0") ``` ### Maven @@ -94,7 +94,7 @@ implementation("com.browserbase.api:stagehand-java:3.19.3") com.browserbase.api stagehand-java - 3.19.3 + 3.20.0 ``` @@ -115,10 +115,8 @@ Multiregion support: see `stagehand-java-example/src/main/java/com/stagehand/api Set your environment variables (from `examples/.env.example`): -- `STAGEHAND_API_URL` - `MODEL_API_KEY` - `BROWSERBASE_API_KEY` -- `BROWSERBASE_PROJECT_ID` ```bash cp examples/.env.example examples/.env @@ -258,8 +256,8 @@ Configure the client using system properties or environment variables: import com.browserbase.api.client.StagehandClient; import com.browserbase.api.client.okhttp.StagehandOkHttpClient; -// Configures using the `stagehand.browserbaseApiKey`, `stagehand.browserbaseProjectId`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties -// Or configures using the `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `MODEL_API_KEY` and `STAGEHAND_BASE_URL` environment variables +// Configures using the `stagehand.browserbaseApiKey`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties +// Or configures using the `BROWSERBASE_API_KEY`, `MODEL_API_KEY` and `STAGEHAND_API_URL` environment variables StagehandClient client = StagehandOkHttpClient.fromEnv(); ``` @@ -271,7 +269,6 @@ import com.browserbase.api.client.okhttp.StagehandOkHttpClient; StagehandClient client = StagehandOkHttpClient.builder() .browserbaseApiKey("My Browserbase API Key") - .browserbaseProjectId("My Browserbase Project ID") .modelApiKey("My Model API Key") .build(); ``` @@ -283,8 +280,8 @@ import com.browserbase.api.client.StagehandClient; import com.browserbase.api.client.okhttp.StagehandOkHttpClient; StagehandClient client = StagehandOkHttpClient.builder() - // Configures using the `stagehand.browserbaseApiKey`, `stagehand.browserbaseProjectId`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties - // Or configures using the `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `MODEL_API_KEY` and `STAGEHAND_BASE_URL` environment variables + // Configures using the `stagehand.browserbaseApiKey`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties + // Or configures using the `BROWSERBASE_API_KEY`, `MODEL_API_KEY` and `STAGEHAND_API_URL` environment variables .fromEnv() .browserbaseApiKey("My Browserbase API Key") .build(); @@ -295,9 +292,10 @@ See this table for the available options: | Setter | System property | Environment variable | Required | Default value | | ---------------------- | -------------------------------- | ------------------------ | -------- | ----------------------------------------- | | `browserbaseApiKey` | `stagehand.browserbaseApiKey` | `BROWSERBASE_API_KEY` | true | - | -| `browserbaseProjectId` | `stagehand.browserbaseProjectId` | `BROWSERBASE_PROJECT_ID` | true | - | | `modelApiKey` | `stagehand.modelApiKey` | `MODEL_API_KEY` | true | - | -| `baseUrl` | `stagehand.baseUrl` | `STAGEHAND_BASE_URL` | true | `"https://api.stagehand.browserbase.com"` | +| `baseUrl` | `stagehand.baseUrl` | `STAGEHAND_API_URL` | false | `"https://api.stagehand.browserbase.com"` | + +`browserbaseProjectId` is deprecated, accepted for backwards compatibility, and ignored. `STAGEHAND_BASE_URL` remains supported as a deprecated fallback when `STAGEHAND_API_URL` is unset. System properties take precedence over environment variables. @@ -345,8 +343,8 @@ import com.browserbase.api.models.sessions.SessionActParams; import com.browserbase.api.models.sessions.SessionActResponse; import java.util.concurrent.CompletableFuture; -// Configures using the `stagehand.browserbaseApiKey`, `stagehand.browserbaseProjectId`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties -// Or configures using the `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `MODEL_API_KEY` and `STAGEHAND_BASE_URL` environment variables +// Configures using the `stagehand.browserbaseApiKey`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties +// Or configures using the `BROWSERBASE_API_KEY`, `MODEL_API_KEY` and `STAGEHAND_API_URL` environment variables StagehandClient client = StagehandOkHttpClient.fromEnv(); SessionActParams params = SessionActParams.builder() @@ -365,8 +363,8 @@ import com.browserbase.api.models.sessions.SessionActParams; import com.browserbase.api.models.sessions.SessionActResponse; import java.util.concurrent.CompletableFuture; -// Configures using the `stagehand.browserbaseApiKey`, `stagehand.browserbaseProjectId`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties -// Or configures using the `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `MODEL_API_KEY` and `STAGEHAND_BASE_URL` environment variables +// Configures using the `stagehand.browserbaseApiKey`, `stagehand.modelApiKey` and `stagehand.baseUrl` system properties +// Or configures using the `BROWSERBASE_API_KEY`, `MODEL_API_KEY` and `STAGEHAND_API_URL` environment variables StagehandClientAsync client = StagehandOkHttpClientAsync.fromEnv(); SessionActParams params = SessionActParams.builder() @@ -432,7 +430,7 @@ client.async().sessions().actStreaming(params) .subscribe(chunk -> { System.out.println(chunk); }) - .onCompleteFuture(); + .onCompleteFuture() .whenComplete((unused, error) -> { if (error != null) { System.out.println("Something went wrong!"); @@ -636,6 +634,21 @@ StagehandClient client = StagehandOkHttpClient.builder() .build(); ``` +If the proxy responds with `407 Proxy Authentication Required`, supply credentials by also configuring `proxyAuthenticator`: + +```java +import com.browserbase.api.client.StagehandClient; +import com.browserbase.api.client.okhttp.StagehandOkHttpClient; +import com.browserbase.api.core.http.ProxyAuthenticator; + +StagehandClient client = StagehandOkHttpClient.builder() + .fromEnv() + .proxy(...) + // Or a custom implementation of `ProxyAuthenticator`. + .proxyAuthenticator(ProxyAuthenticator.basic("username", "password")) + .build(); +``` + ### Connection pooling To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods: @@ -873,7 +886,9 @@ In rare cases, the API may return a response that doesn't match the expected typ By default, the SDK will not throw an exception in this case. It will throw [`StagehandInvalidDataException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/StagehandInvalidDataException.kt) only if you directly access the property. -If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`: +Validating the response is _not_ forwards compatible with new types from the API for existing fields. + +If you would still prefer to check that the response is completely well-typed upfront, then either call `validate()`: ```java import com.browserbase.api.models.sessions.SessionActResponse; diff --git a/build.gradle.kts b/build.gradle.kts index 7d91da7..4602d16 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.browserbase.api" - version = "3.19.3" // x-release-please-version + version = "3.20.0" // x-release-please-version } subprojects { @@ -21,7 +21,6 @@ subprojects { group = "Verification" description = "Verifies all source files are formatted." } - apply(plugin = "org.jetbrains.dokka") } subprojects { 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/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt index 688abb6..2d3b42f 100644 --- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt +++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt @@ -8,9 +8,11 @@ import com.browserbase.api.core.http.HttpMethod import com.browserbase.api.core.http.HttpRequest import com.browserbase.api.core.http.HttpRequestBody import com.browserbase.api.core.http.HttpResponse +import com.browserbase.api.core.http.ProxyAuthenticator import com.browserbase.api.errors.StagehandIoException import java.io.IOException import java.io.InputStream +import java.io.OutputStream import java.net.Proxy import java.time.Duration import java.util.concurrent.CancellationException @@ -20,10 +22,12 @@ import java.util.concurrent.TimeUnit import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509TrustManager +import kotlin.jvm.optionals.getOrNull import okhttp3.Call import okhttp3.Callback import okhttp3.ConnectionPool import okhttp3.Dispatcher +import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType @@ -33,6 +37,8 @@ import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import okio.BufferedSink +import okio.buffer +import okio.sink class OkHttpClient internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient { @@ -41,7 +47,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie val call = newCall(request, requestOptions) return try { - call.execute().toResponse() + call.execute().toHttpResponse() } catch (e: IOException) { throw StagehandIoException("Request failed", e) } finally { @@ -59,7 +65,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie call.enqueue( object : Callback { override fun onResponse(call: Call, response: Response) { - future.complete(response.toResponse()) + future.complete(response.toHttpResponse()) } override fun onFailure(call: Call, e: IOException) { @@ -97,7 +103,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie clientBuilder.addNetworkInterceptor( HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("x-bb-api-key") - redactHeader("x-bb-project-id") redactHeader("x-model-api-key") } ) @@ -115,89 +120,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie return client.newCall(request.toRequest(client)) } - private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request { - var body: RequestBody? = body?.toRequestBody() - if (body == null && requiresBody(method)) { - body = "".toRequestBody() - } - - val builder = Request.Builder().url(toUrl()).method(method.name, body) - headers.names().forEach { name -> - headers.values(name).forEach { builder.addHeader(name, it) } - } - - if ( - !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0 - ) { - builder.addHeader( - "X-Stainless-Read-Timeout", - Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(), - ) - } - if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) { - builder.addHeader( - "X-Stainless-Timeout", - Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(), - ) - } - - return builder.build() - } - - /** `OkHttpClient` always requires a request body for some methods. */ - private fun requiresBody(method: HttpMethod): Boolean = - when (method) { - HttpMethod.POST, - HttpMethod.PUT, - HttpMethod.PATCH -> true - else -> false - } - - private fun HttpRequest.toUrl(): String { - val builder = baseUrl.toHttpUrl().newBuilder() - pathSegments.forEach(builder::addPathSegment) - queryParams.keys().forEach { key -> - queryParams.values(key).forEach { builder.addQueryParameter(key, it) } - } - - return builder.toString() - } - - private fun HttpRequestBody.toRequestBody(): RequestBody { - val mediaType = contentType()?.toMediaType() - val length = contentLength() - - return object : RequestBody() { - override fun contentType(): MediaType? = mediaType - - override fun contentLength(): Long = length - - override fun isOneShot(): Boolean = !repeatable() - - override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream()) - } - } - - private fun Response.toResponse(): HttpResponse { - val headers = headers.toHeaders() - - return object : HttpResponse { - override fun statusCode(): Int = code - - override fun headers(): Headers = headers - - override fun body(): InputStream = body!!.byteStream() - - override fun close() = body!!.close() - } - } - - private fun okhttp3.Headers.toHeaders(): Headers { - val headersBuilder = Headers.builder() - forEach { (name, value) -> headersBuilder.put(name, value) } - return headersBuilder.build() - } - companion object { @JvmStatic fun builder() = Builder() } @@ -206,6 +128,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie private var timeout: Timeout = Timeout.default() private var proxy: Proxy? = null + private var proxyAuthenticator: ProxyAuthenticator? = null private var maxIdleConnections: Int? = null private var keepAliveDuration: Duration? = null private var dispatcherExecutorService: ExecutorService? = null @@ -219,6 +142,10 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie fun proxy(proxy: Proxy?) = apply { this.proxy = proxy } + fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply { + this.proxyAuthenticator = proxyAuthenticator + } + /** * Sets the maximum number of idle connections kept by the underlying [ConnectionPool]. * @@ -268,6 +195,19 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie .callTimeout(timeout.request()) .proxy(proxy) .apply { + proxyAuthenticator?.let { auth -> + proxyAuthenticator { route, response -> + auth + .authenticate( + route?.proxy ?: Proxy.NO_PROXY, + response.request.toHttpRequest(), + response.toHttpResponse(), + ) + .getOrNull() + ?.toRequest(client = null) + } + } + dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) } val maxIdleConnections = maxIdleConnections @@ -307,3 +247,126 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie ) } } + +private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient?): Request { + var body: RequestBody? = body?.toRequestBody() + if (body == null && requiresBody(method)) { + body = "".toRequestBody() + } + + val builder = Request.Builder().url(toUrl()).method(method.name, body) + headers.names().forEach { name -> headers.values(name).forEach { builder.addHeader(name, it) } } + + if (client != null) { + if ( + !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0 + ) { + builder.addHeader( + "X-Stainless-Read-Timeout", + Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(), + ) + } + if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) { + builder.addHeader( + "X-Stainless-Timeout", + Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(), + ) + } + } + + return builder.build() +} + +/** `OkHttpClient` always requires a request body for some methods. */ +private fun requiresBody(method: HttpMethod): Boolean = + when (method) { + HttpMethod.POST, + HttpMethod.PUT, + HttpMethod.PATCH -> true + else -> false + } + +private fun HttpRequest.toUrl(): String { + val builder = baseUrl.toHttpUrl().newBuilder() + pathSegments.forEach(builder::addPathSegment) + queryParams.keys().forEach { key -> + queryParams.values(key).forEach { builder.addQueryParameter(key, it) } + } + + return builder.toString() +} + +private fun HttpRequestBody.toRequestBody(): RequestBody { + val mediaType = contentType()?.toMediaType() + val length = contentLength() + + return object : RequestBody() { + override fun contentType(): MediaType? = mediaType + + override fun contentLength(): Long = length + + override fun isOneShot(): Boolean = !repeatable() + + override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream()) + } +} + +private fun Request.toHttpRequest(): HttpRequest { + val builder = HttpRequest.builder().method(HttpMethod.valueOf(method)).baseUrl(url.toBaseUrl()) + url.pathSegments.forEach(builder::addPathSegment) + url.queryParameterNames.forEach { name -> + url.queryParameterValues(name).filterNotNull().forEach { builder.putQueryParam(name, it) } + } + headers.forEach { (name, value) -> builder.putHeader(name, value) } + body?.let { builder.body(it.toHttpRequestBody()) } + return builder.build() +} + +private fun HttpUrl.toBaseUrl(): String = buildString { + append(scheme).append("://").append(host) + if (port != HttpUrl.defaultPort(scheme)) { + append(":").append(port) + } +} + +private fun RequestBody.toHttpRequestBody(): HttpRequestBody { + val mediaType = contentType()?.toString() + val length = contentLength() + val isOneShot = isOneShot() + val source = this + return object : HttpRequestBody { + override fun contentType(): String? = mediaType + + override fun contentLength(): Long = length + + override fun repeatable(): Boolean = !isOneShot + + override fun writeTo(outputStream: OutputStream) { + val sink = outputStream.sink().buffer() + source.writeTo(sink) + sink.flush() + } + + override fun close() {} + } +} + +private fun Response.toHttpResponse(): HttpResponse { + val headers = headers.toHeaders() + + return object : HttpResponse { + override fun statusCode(): Int = code + + override fun headers(): Headers = headers + + override fun body(): InputStream = body!!.byteStream() + + override fun close() = body!!.close() + } +} + +private fun okhttp3.Headers.toHeaders(): Headers { + val headersBuilder = Headers.builder() + forEach { (name, value) -> headersBuilder.put(name, value) } + return headersBuilder.build() +} diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt index 2137ec6..972d3e3 100644 --- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt +++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt @@ -10,6 +10,7 @@ import com.browserbase.api.core.Timeout import com.browserbase.api.core.http.AsyncStreamResponse import com.browserbase.api.core.http.Headers import com.browserbase.api.core.http.HttpClient +import com.browserbase.api.core.http.ProxyAuthenticator import com.browserbase.api.core.http.QueryParams import com.browserbase.api.core.jsonMapper import com.fasterxml.jackson.databind.json.JsonMapper @@ -49,6 +50,7 @@ class StagehandOkHttpClient private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null + private var proxyAuthenticator: ProxyAuthenticator? = null private var maxIdleConnections: Int? = null private var keepAliveDuration: Duration? = null private var sslSocketFactory: SSLSocketFactory? = null @@ -79,6 +81,20 @@ class StagehandOkHttpClient private constructor() { /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication + * Required`. + */ + fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply { + this.proxyAuthenticator = proxyAuthenticator + } + + /** + * Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`. + */ + fun proxyAuthenticator(proxyAuthenticator: Optional) = + proxyAuthenticator(proxyAuthenticator.getOrNull()) + /** * The maximum number of idle connections kept by the underlying OkHttp connection pool. * @@ -230,6 +246,9 @@ class StagehandOkHttpClient private constructor() { /** * Whether to call `validate` on every response before returning it. * + * Setting this to `true` is _not_ forwards compatible with new types from the API for + * existing fields. + * * Defaults to false, which means the shape of the response will not be validated upfront. * Instead, validation will only occur for the parts of the response that are accessed. */ @@ -276,11 +295,21 @@ class StagehandOkHttpClient private constructor() { clientOptions.browserbaseApiKey(browserbaseApiKey) } - /** Your [Browserbase Project ID](https://www.browserbase.com/settings) */ - fun browserbaseProjectId(browserbaseProjectId: String) = apply { + /** + * Deprecated. Browserbase API keys are now project-scoped, so this value is no longer + * required. + */ + fun browserbaseProjectId(browserbaseProjectId: String?) = apply { clientOptions.browserbaseProjectId(browserbaseProjectId) } + /** + * Alias for calling [Builder.browserbaseProjectId] with + * `browserbaseProjectId.orElse(null)`. + */ + fun browserbaseProjectId(browserbaseProjectId: Optional) = + browserbaseProjectId(browserbaseProjectId.getOrNull()) + /** Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) */ fun modelApiKey(modelApiKey: String) = apply { clientOptions.modelApiKey(modelApiKey) } @@ -383,6 +412,7 @@ class StagehandOkHttpClient private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .proxyAuthenticator(proxyAuthenticator) .maxIdleConnections(maxIdleConnections) .keepAliveDuration(keepAliveDuration) .dispatcherExecutorService(dispatcherExecutorService) diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt index ffacd17..321443a 100644 --- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt +++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt @@ -10,6 +10,7 @@ import com.browserbase.api.core.Timeout import com.browserbase.api.core.http.AsyncStreamResponse import com.browserbase.api.core.http.Headers import com.browserbase.api.core.http.HttpClient +import com.browserbase.api.core.http.ProxyAuthenticator import com.browserbase.api.core.http.QueryParams import com.browserbase.api.core.jsonMapper import com.fasterxml.jackson.databind.json.JsonMapper @@ -49,6 +50,7 @@ class StagehandOkHttpClientAsync private constructor() { private var clientOptions: ClientOptions.Builder = ClientOptions.builder() private var dispatcherExecutorService: ExecutorService? = null private var proxy: Proxy? = null + private var proxyAuthenticator: ProxyAuthenticator? = null private var maxIdleConnections: Int? = null private var keepAliveDuration: Duration? = null private var sslSocketFactory: SSLSocketFactory? = null @@ -79,6 +81,20 @@ class StagehandOkHttpClientAsync private constructor() { /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */ fun proxy(proxy: Optional) = proxy(proxy.getOrNull()) + /** + * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication + * Required`. + */ + fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply { + this.proxyAuthenticator = proxyAuthenticator + } + + /** + * Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`. + */ + fun proxyAuthenticator(proxyAuthenticator: Optional) = + proxyAuthenticator(proxyAuthenticator.getOrNull()) + /** * The maximum number of idle connections kept by the underlying OkHttp connection pool. * @@ -230,6 +246,9 @@ class StagehandOkHttpClientAsync private constructor() { /** * Whether to call `validate` on every response before returning it. * + * Setting this to `true` is _not_ forwards compatible with new types from the API for + * existing fields. + * * Defaults to false, which means the shape of the response will not be validated upfront. * Instead, validation will only occur for the parts of the response that are accessed. */ @@ -276,11 +295,21 @@ class StagehandOkHttpClientAsync private constructor() { clientOptions.browserbaseApiKey(browserbaseApiKey) } - /** Your [Browserbase Project ID](https://www.browserbase.com/settings) */ - fun browserbaseProjectId(browserbaseProjectId: String) = apply { + /** + * Deprecated. Browserbase API keys are now project-scoped, so this value is no longer + * required. + */ + fun browserbaseProjectId(browserbaseProjectId: String?) = apply { clientOptions.browserbaseProjectId(browserbaseProjectId) } + /** + * Alias for calling [Builder.browserbaseProjectId] with + * `browserbaseProjectId.orElse(null)`. + */ + fun browserbaseProjectId(browserbaseProjectId: Optional) = + browserbaseProjectId(browserbaseProjectId.getOrNull()) + /** Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) */ fun modelApiKey(modelApiKey: String) = apply { clientOptions.modelApiKey(modelApiKey) } @@ -383,6 +412,7 @@ class StagehandOkHttpClientAsync private constructor() { OkHttpClient.builder() .timeout(clientOptions.timeout()) .proxy(proxy) + .proxyAuthenticator(proxyAuthenticator) .maxIdleConnections(maxIdleConnections) .keepAliveDuration(keepAliveDuration) .dispatcherExecutorService(dispatcherExecutorService) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt index ac6a5ef..90fa08b 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt @@ -80,6 +80,9 @@ private constructor( /** * Whether to call `validate` on every response before returning it. * + * Setting this to `true` is _not_ forwards compatible with new types from the API for existing + * fields. + * * Defaults to false, which means the shape of the response will not be validated upfront. * Instead, validation will only occur for the parts of the response that are accessed. */ @@ -109,8 +112,7 @@ private constructor( @get:JvmName("maxRetries") val maxRetries: Int, /** Your [Browserbase API Key](https://www.browserbase.com/settings) */ @get:JvmName("browserbaseApiKey") val browserbaseApiKey: String, - /** Your [Browserbase Project ID](https://www.browserbase.com/settings) */ - @get:JvmName("browserbaseProjectId") val browserbaseProjectId: String, + private val browserbaseProjectId: String?, /** Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) */ @get:JvmName("modelApiKey") val modelApiKey: String, ) { @@ -128,6 +130,11 @@ private constructor( */ fun baseUrl(): String = baseUrl ?: PRODUCTION_URL + /** + * Deprecated. Browserbase API keys are now project-scoped, so this value is no longer required. + */ + fun browserbaseProjectId(): Optional = Optional.ofNullable(browserbaseProjectId) + fun toBuilder() = Builder().from(this) companion object { @@ -141,7 +148,6 @@ private constructor( * ```java * .httpClient() * .browserbaseApiKey() - * .browserbaseProjectId() * .modelApiKey() * ``` */ @@ -270,6 +276,9 @@ private constructor( /** * Whether to call `validate` on every response before returning it. * + * Setting this to `true` is _not_ forwards compatible with new types from the API for + * existing fields. + * * Defaults to false, which means the shape of the response will not be validated upfront. * Instead, validation will only occur for the parts of the response that are accessed. */ @@ -316,11 +325,21 @@ private constructor( this.browserbaseApiKey = browserbaseApiKey } - /** Your [Browserbase Project ID](https://www.browserbase.com/settings) */ - fun browserbaseProjectId(browserbaseProjectId: String) = apply { + /** + * Deprecated. Browserbase API keys are now project-scoped, so this value is no longer + * required. Accepted for backwards compatibility; it is ignored. + */ + fun browserbaseProjectId(browserbaseProjectId: String?) = apply { this.browserbaseProjectId = browserbaseProjectId } + /** + * Alias for calling [Builder.browserbaseProjectId] with + * `browserbaseProjectId.orElse(null)`. + */ + fun browserbaseProjectId(browserbaseProjectId: Optional) = + browserbaseProjectId(browserbaseProjectId.getOrNull()) + /** Your LLM provider API key (e.g. OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) */ fun modelApiKey(modelApiKey: String) = apply { this.modelApiKey = modelApiKey } @@ -411,28 +430,34 @@ private constructor( * * See this table for the available options: * - * |Setter |System property |Environment variable |Required|Default value | - * |----------------------|--------------------------------|------------------------|--------|-----------------------------------------| - * |`browserbaseApiKey` |`stagehand.browserbaseApiKey` |`BROWSERBASE_API_KEY` |true |- | - * |`browserbaseProjectId`|`stagehand.browserbaseProjectId`|`BROWSERBASE_PROJECT_ID`|true |- | - * |`modelApiKey` |`stagehand.modelApiKey` |`MODEL_API_KEY` |true |- | - * |`baseUrl` |`stagehand.baseUrl` |`STAGEHAND_BASE_URL` |true |`"https://api.stagehand.browserbase.com"`| + * |Setter |System property |Environment variable |Required|Default value | + * |-------------------|-----------------------------|---------------------|--------|-----------------------------------------| + * |`browserbaseApiKey`|`stagehand.browserbaseApiKey`|`BROWSERBASE_API_KEY`|true |- | + * |`modelApiKey` |`stagehand.modelApiKey` |`MODEL_API_KEY` |true |- | + * |`baseUrl` |`stagehand.baseUrl` |`STAGEHAND_API_URL` |false |`"https://api.stagehand.browserbase.com"`| * * System properties take precedence over environment variables. */ - fun fromEnv() = apply { - (System.getProperty("stagehand.baseUrl") ?: System.getenv("STAGEHAND_BASE_URL"))?.let { - baseUrl(it) - } - (System.getProperty("stagehand.browserbaseApiKey") - ?: System.getenv("BROWSERBASE_API_KEY")) + fun fromEnv() = fromEnv(System::getenv) + + internal fun fromEnv(getEnv: (String) -> String?) = apply { + (System.getProperty("stagehand.baseUrl") + ?: getEnv("STAGEHAND_API_URL") + ?: getEnv("STAGEHAND_BASE_URL")) + ?.let { baseUrl(it) } + (System.getProperty("stagehand.browserbaseApiKey") ?: getEnv("BROWSERBASE_API_KEY")) ?.let { browserbaseApiKey(it) } - (System.getProperty("stagehand.browserbaseProjectId") - ?: System.getenv("BROWSERBASE_PROJECT_ID")) - ?.let { browserbaseProjectId(it) } - (System.getProperty("stagehand.modelApiKey") ?: System.getenv("MODEL_API_KEY"))?.let { + (System.getProperty("stagehand.modelApiKey") ?: getEnv("MODEL_API_KEY"))?.let { modelApiKey(it) } + getEnv("STAGEHAND_CUSTOM_HEADERS")?.let { customHeadersEnv -> + for (line in customHeadersEnv.split("\n")) { + val colon = line.indexOf(':') + if (colon >= 0) { + putHeader(line.substring(0, colon).trim(), line.substring(colon + 1).trim()) + } + } + } } /** @@ -444,7 +469,6 @@ private constructor( * ```java * .httpClient() * .browserbaseApiKey() - * .browserbaseProjectId() * .modelApiKey() * ``` * @@ -472,7 +496,6 @@ private constructor( ) val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper()) val browserbaseApiKey = checkRequired("browserbaseApiKey", browserbaseApiKey) - val browserbaseProjectId = checkRequired("browserbaseProjectId", browserbaseProjectId) val modelApiKey = checkRequired("modelApiKey", modelApiKey) val headers = Headers.builder() @@ -493,11 +516,6 @@ private constructor( headers.replace("x-bb-api-key", it) } } - browserbaseProjectId.let { - if (!it.isEmpty()) { - headers.replace("x-bb-project-id", it) - } - } modelApiKey.let { if (!it.isEmpty()) { headers.replace("x-model-api-key", it) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt index 5200d47..65b7e72 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ObjectMappers.kt @@ -29,7 +29,9 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoField -fun jsonMapper(): JsonMapper = +fun jsonMapper(): JsonMapper = JSON_MAPPER + +private val JSON_MAPPER: JsonMapper = JsonMapper.builder() .addModule(kotlinModule()) .addModule(Jdk8Module()) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/RequestOptions.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/RequestOptions.kt index 9674a09..795cdd5 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/RequestOptions.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/RequestOptions.kt @@ -33,6 +33,15 @@ class RequestOptions private constructor(val responseValidation: Boolean?, val t private var responseValidation: Boolean? = null private var timeout: Timeout? = null + /** + * Whether to call `validate` on the response before returning it. + * + * Setting this to `true` is _not_ forwards compatible with new types from the API for + * existing fields. + * + * Defaults to false, which means the shape of the response will not be validated upfront. + * Instead, validation will only occur for the parts of the response that are accessed. + */ fun responseValidation(responseValidation: Boolean) = apply { this.responseValidation = responseValidation } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/ProxyAuthenticator.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/ProxyAuthenticator.kt new file mode 100644 index 0000000..75e750b --- /dev/null +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/ProxyAuthenticator.kt @@ -0,0 +1,59 @@ +package com.browserbase.api.core.http + +import java.net.Proxy +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.util.Base64 +import java.util.Optional + +/** + * Provides credentials when an HTTP proxy responds with `407 Proxy Authentication Required`. + * + * Implementations inspect the 407 [response] (typically its `Proxy-Authenticate` header) and return + * the request to retry with a `Proxy-Authorization` header set, or [Optional.empty] to abandon + * authentication and surface the 407 to the caller. + * + * Implementations must be thread-safe; they may be invoked concurrently from multiple HTTP calls. + */ +fun interface ProxyAuthenticator { + + /** + * @param proxy the proxy that produced the challenge, or [Proxy.NO_PROXY] if the route is not + * yet established + * @param request the request that produced [response] + * @param response the 407 challenge response + * @return the retry request to send (typically [request] with a `Proxy-Authorization` header + * added), or [Optional.empty] to abandon authentication + */ + fun authenticate( + proxy: Proxy, + request: HttpRequest, + response: HttpResponse, + ): Optional + + companion object { + + /** + * A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the ISO-8859-1 + * charset. + */ + @JvmStatic + fun basic(username: String, password: String): ProxyAuthenticator = + basic(username, password, StandardCharsets.ISO_8859_1) + + /** + * A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the given [charset]. + */ + @JvmStatic + fun basic(username: String, password: String, charset: Charset): ProxyAuthenticator { + val token = + Base64.getEncoder().encodeToString("$username:$password".toByteArray(charset)) + val headerValue = "Basic $token" + return ProxyAuthenticator { _, request, _ -> + Optional.of( + request.toBuilder().putHeader("Proxy-Authorization", headerValue).build() + ) + } + } + } +} diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/BadRequestException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/BadRequestException.kt index c56095d..3f21e7c 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/BadRequestException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/BadRequestException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class BadRequestException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("400: $body", cause) { + StagehandServiceException( + "400: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 400 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt index c2b0d58..1f782c0 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt @@ -5,6 +5,7 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -14,7 +15,11 @@ private constructor( private val headers: Headers, private val body: JsonValue, cause: Throwable?, -) : StagehandServiceException("$statusCode: $body", cause) { +) : + StagehandServiceException( + "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = statusCode diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/NotFoundException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/NotFoundException.kt index b8dcbd2..b3e3f6f 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/NotFoundException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/NotFoundException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class NotFoundException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("404: $body", cause) { + StagehandServiceException( + "404: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 404 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/PermissionDeniedException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/PermissionDeniedException.kt index 4c37a8a..79c4ddb 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/PermissionDeniedException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/PermissionDeniedException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class PermissionDeniedException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("403: $body", cause) { + StagehandServiceException( + "403: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 403 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/RateLimitException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/RateLimitException.kt index b72ef4f..34c3d23 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/RateLimitException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/RateLimitException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class RateLimitException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("429: $body", cause) { + StagehandServiceException( + "429: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 429 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt index 0dfdd2f..8c902f5 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt @@ -5,6 +5,7 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -14,7 +15,11 @@ private constructor( private val headers: Headers, private val body: JsonValue, cause: Throwable?, -) : StagehandServiceException("$statusCode: $body", cause) { +) : + StagehandServiceException( + "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = statusCode diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnauthorizedException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnauthorizedException.kt index 23ed90e..37597f5 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnauthorizedException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnauthorizedException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class UnauthorizedException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("401: $body", cause) { + StagehandServiceException( + "401: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 401 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt index c375faa..e013e77 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt @@ -5,6 +5,7 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -14,7 +15,11 @@ private constructor( private val headers: Headers, private val body: JsonValue, cause: Throwable?, -) : StagehandServiceException("$statusCode: $body", cause) { +) : + StagehandServiceException( + "$statusCode: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = statusCode diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnprocessableEntityException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnprocessableEntityException.kt index a162609..233efac 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnprocessableEntityException.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnprocessableEntityException.kt @@ -5,12 +5,16 @@ package com.browserbase.api.errors import com.browserbase.api.core.JsonValue import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers +import com.browserbase.api.core.jsonMapper import java.util.Optional import kotlin.jvm.optionals.getOrNull class UnprocessableEntityException private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) : - StagehandServiceException("422: $body", cause) { + StagehandServiceException( + "422: ${if (body.isMissing()) "Unknown" else jsonMapper().writeValueAsString(body)}", + cause, + ) { override fun statusCode(): Int = 422 diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt index 2ce760b..a77aa3d 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt @@ -288,6 +288,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Action = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt index 4e46131..b20ac9d 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt @@ -260,6 +260,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): ModelConfig = apply { if (validated) { return@apply @@ -353,6 +361,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Headers = apply { if (validated) { return@apply @@ -502,6 +519,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Provider = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt index 6d92b39..a72cbeb 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt @@ -543,6 +543,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -622,6 +631,35 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, unless + * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of + * the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = input.accept(new Input.Visitor>() { + * @Override + * public Optional visitString(String string) { + * return Optional.of(string.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in [visitor] + * and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { string != null -> visitor.visitString(string) @@ -631,6 +669,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Input = apply { if (validated) { return@apply @@ -945,6 +992,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Options = apply { if (validated) { return@apply @@ -1000,6 +1056,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = model.accept(new Model.Visitor>() { + * @Override + * public Optional visitConfig(ModelConfig config) { + * return Optional.of(config.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { config != null -> visitor.visitConfig(config) @@ -1009,6 +1095,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Model = apply { if (validated) { return@apply @@ -1213,6 +1309,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Variables = apply { if (validated) { return@apply @@ -1370,6 +1476,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt index 62bec51..90bf268 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt @@ -164,6 +164,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionActResponse = apply { if (validated) { return@apply @@ -332,6 +340,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply @@ -616,6 +633,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Result = apply { if (validated) { return@apply @@ -943,6 +970,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Action = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt index cf9a245..ff13fdc 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndParams.kt @@ -331,6 +331,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndResponse.kt index 3a886e7..d3f370f 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionEndResponse.kt @@ -130,6 +130,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionEndResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt index f68232a..d124ba0 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteParams.kt @@ -15,6 +15,7 @@ import com.browserbase.api.core.checkRequired import com.browserbase.api.core.getOrThrow import com.browserbase.api.core.http.Headers import com.browserbase.api.core.http.QueryParams +import com.browserbase.api.core.toImmutable import com.browserbase.api.errors.StagehandInvalidDataException import com.fasterxml.jackson.annotation.JsonAnyGetter import com.fasterxml.jackson.annotation.JsonAnySetter @@ -616,6 +617,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -973,6 +983,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): AgentConfig = apply { if (validated) { return@apply @@ -1038,6 +1057,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = executionModel.accept(new ExecutionModel.Visitor>() { + * @Override + * public Optional visitModelConfig(ModelConfig modelConfig) { + * return Optional.of(modelConfig.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { modelConfig != null -> visitor.visitModelConfig(modelConfig) @@ -1047,6 +1096,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): ExecutionModel = apply { if (validated) { return@apply @@ -1288,6 +1347,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Mode = apply { if (validated) { return@apply @@ -1350,6 +1419,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = model.accept(new Model.Visitor>() { + * @Override + * public Optional visitConfig(ModelConfig config) { + * return Optional.of(config.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { config != null -> visitor.visitConfig(config) @@ -1359,6 +1458,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Model = apply { if (validated) { return@apply @@ -1609,6 +1718,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Provider = apply { if (validated) { return@apply @@ -1688,6 +1807,7 @@ private constructor( private val maxSteps: JsonField, private val toolTimeout: JsonField, private val useSearch: JsonField, + private val variables: JsonField, private val additionalProperties: MutableMap, ) { @@ -1708,7 +1828,18 @@ private constructor( @JsonProperty("useSearch") @ExcludeMissing useSearch: JsonField = JsonMissing.of(), - ) : this(instruction, highlightCursor, maxSteps, toolTimeout, useSearch, mutableMapOf()) + @JsonProperty("variables") + @ExcludeMissing + variables: JsonField = JsonMissing.of(), + ) : this( + instruction, + highlightCursor, + maxSteps, + toolTimeout, + useSearch, + variables, + mutableMapOf(), + ) /** * Natural language instruction for the agent @@ -1750,6 +1881,14 @@ private constructor( */ fun useSearch(): Optional = useSearch.getOptional("useSearch") + /** + * Variables available to the agent via %variableName% syntax in supported tools + * + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun variables(): Optional = variables.getOptional("variables") + /** * Returns the raw JSON value of [instruction]. * @@ -1792,6 +1931,15 @@ private constructor( */ @JsonProperty("useSearch") @ExcludeMissing fun _useSearch(): JsonField = useSearch + /** + * Returns the raw JSON value of [variables]. + * + * Unlike [variables], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("variables") + @ExcludeMissing + fun _variables(): JsonField = variables + @JsonAnySetter private fun putAdditionalProperty(key: String, value: JsonValue) { additionalProperties.put(key, value) @@ -1825,6 +1973,7 @@ private constructor( private var maxSteps: JsonField = JsonMissing.of() private var toolTimeout: JsonField = JsonMissing.of() private var useSearch: JsonField = JsonMissing.of() + private var variables: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @JvmSynthetic @@ -1834,6 +1983,7 @@ private constructor( maxSteps = executeOptions.maxSteps toolTimeout = executeOptions.toolTimeout useSearch = executeOptions.useSearch + variables = executeOptions.variables additionalProperties = executeOptions.additionalProperties.toMutableMap() } @@ -1904,6 +2054,18 @@ private constructor( */ fun useSearch(useSearch: JsonField) = apply { this.useSearch = useSearch } + /** Variables available to the agent via %variableName% syntax in supported tools */ + fun variables(variables: Variables) = variables(JsonField.of(variables)) + + /** + * Sets [Builder.variables] to an arbitrary JSON value. + * + * You should usually call [Builder.variables] with a well-typed [Variables] value + * instead. This method is primarily for setting the field to an undocumented or not yet + * supported value. + */ + fun variables(variables: JsonField) = apply { this.variables = variables } + fun additionalProperties(additionalProperties: Map) = apply { this.additionalProperties.clear() putAllAdditionalProperties(additionalProperties) @@ -1942,12 +2104,22 @@ private constructor( maxSteps, toolTimeout, useSearch, + variables, additionalProperties.toMutableMap(), ) } private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): ExecuteOptions = apply { if (validated) { return@apply @@ -1958,6 +2130,7 @@ private constructor( maxSteps() toolTimeout() useSearch() + variables().ifPresent { it.validate() } validated = true } @@ -1981,7 +2154,121 @@ private constructor( (if (highlightCursor.asKnown().isPresent) 1 else 0) + (if (maxSteps.asKnown().isPresent) 1 else 0) + (if (toolTimeout.asKnown().isPresent) 1 else 0) + - (if (useSearch.asKnown().isPresent) 1 else 0) + (if (useSearch.asKnown().isPresent) 1 else 0) + + (variables.asKnown().getOrNull()?.validity() ?: 0) + + /** Variables available to the agent via %variableName% syntax in supported tools */ + class Variables + @JsonCreator + private constructor( + @com.fasterxml.jackson.annotation.JsonValue + private val additionalProperties: Map + ) { + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = additionalProperties + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Variables]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Variables]. */ + class Builder internal constructor() { + + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(variables: Variables) = apply { + additionalProperties = variables.additionalProperties.toMutableMap() + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = + apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { + additionalProperties.remove(key) + } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [Variables]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Variables = Variables(additionalProperties.toImmutable()) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ + fun validate(): Variables = apply { + if (validated) { + return@apply + } + + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: StagehandInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic + internal fun validity(): Int = + additionalProperties.count { (_, value) -> !value.isNull() && !value.isMissing() } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Variables && additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = "Variables{additionalProperties=$additionalProperties}" + } override fun equals(other: Any?): Boolean { if (this === other) { @@ -1994,6 +2281,7 @@ private constructor( maxSteps == other.maxSteps && toolTimeout == other.toolTimeout && useSearch == other.useSearch && + variables == other.variables && additionalProperties == other.additionalProperties } @@ -2004,6 +2292,7 @@ private constructor( maxSteps, toolTimeout, useSearch, + variables, additionalProperties, ) } @@ -2011,7 +2300,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "ExecuteOptions{instruction=$instruction, highlightCursor=$highlightCursor, maxSteps=$maxSteps, toolTimeout=$toolTimeout, useSearch=$useSearch, additionalProperties=$additionalProperties}" + "ExecuteOptions{instruction=$instruction, highlightCursor=$highlightCursor, maxSteps=$maxSteps, toolTimeout=$toolTimeout, useSearch=$useSearch, variables=$variables, additionalProperties=$additionalProperties}" } /** Whether to stream the response via SSE */ @@ -2108,6 +2397,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteResponse.kt index 9b16655..c2837fc 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExecuteResponse.kt @@ -164,6 +164,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionExecuteResponse = apply { if (validated) { return@apply @@ -339,6 +347,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply @@ -680,6 +697,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Result = apply { if (validated) { return@apply @@ -1095,6 +1122,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Action = apply { if (validated) { return@apply @@ -1234,6 +1271,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Metadata = apply { if (validated) { return@apply @@ -1564,6 +1611,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Usage = apply { if (validated) { return@apply @@ -1807,6 +1864,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): CacheEntry = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt index d732dbe..5c5fe92 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractParams.kt @@ -11,6 +11,7 @@ import com.browserbase.api.core.JsonMissing import com.browserbase.api.core.JsonValue import com.browserbase.api.core.Params import com.browserbase.api.core.allMaxBy +import com.browserbase.api.core.checkKnown import com.browserbase.api.core.getOrThrow import com.browserbase.api.core.http.Headers import com.browserbase.api.core.http.QueryParams @@ -564,6 +565,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -623,6 +633,7 @@ private constructor( class Options @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( + private val ignoreSelectors: JsonField>, private val model: JsonField, private val selector: JsonField, private val timeout: JsonField, @@ -631,12 +642,24 @@ private constructor( @JsonCreator private constructor( + @JsonProperty("ignoreSelectors") + @ExcludeMissing + ignoreSelectors: JsonField> = JsonMissing.of(), @JsonProperty("model") @ExcludeMissing model: JsonField = JsonMissing.of(), @JsonProperty("selector") @ExcludeMissing selector: JsonField = JsonMissing.of(), @JsonProperty("timeout") @ExcludeMissing timeout: JsonField = JsonMissing.of(), - ) : this(model, selector, timeout, mutableMapOf()) + ) : this(ignoreSelectors, model, selector, timeout, mutableMapOf()) + + /** + * Selectors for elements and subtrees that should be excluded from extraction + * + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if + * the server responded with an unexpected value). + */ + fun ignoreSelectors(): Optional> = + ignoreSelectors.getOptional("ignoreSelectors") /** * Model configuration object or model name string (e.g., 'openai/gpt-5-nano') @@ -662,6 +685,16 @@ private constructor( */ fun timeout(): Optional = timeout.getOptional("timeout") + /** + * Returns the raw JSON value of [ignoreSelectors]. + * + * Unlike [ignoreSelectors], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("ignoreSelectors") + @ExcludeMissing + fun _ignoreSelectors(): JsonField> = ignoreSelectors + /** * Returns the raw JSON value of [model]. * @@ -704,6 +737,7 @@ private constructor( /** A builder for [Options]. */ class Builder internal constructor() { + private var ignoreSelectors: JsonField>? = null private var model: JsonField = JsonMissing.of() private var selector: JsonField = JsonMissing.of() private var timeout: JsonField = JsonMissing.of() @@ -711,12 +745,40 @@ private constructor( @JvmSynthetic internal fun from(options: Options) = apply { + ignoreSelectors = options.ignoreSelectors.map { it.toMutableList() } model = options.model selector = options.selector timeout = options.timeout additionalProperties = options.additionalProperties.toMutableMap() } + /** Selectors for elements and subtrees that should be excluded from extraction */ + fun ignoreSelectors(ignoreSelectors: List) = + ignoreSelectors(JsonField.of(ignoreSelectors)) + + /** + * Sets [Builder.ignoreSelectors] to an arbitrary JSON value. + * + * You should usually call [Builder.ignoreSelectors] with a well-typed `List` + * value instead. This method is primarily for setting the field to an undocumented or + * not yet supported value. + */ + fun ignoreSelectors(ignoreSelectors: JsonField>) = apply { + this.ignoreSelectors = ignoreSelectors.map { it.toMutableList() } + } + + /** + * Adds a single [String] to [ignoreSelectors]. + * + * @throws IllegalStateException if the field was previously set to a non-list. + */ + fun addIgnoreSelector(ignoreSelector: String) = apply { + ignoreSelectors = + (ignoreSelectors ?: JsonField.of(mutableListOf())).also { + checkKnown("ignoreSelectors", it).add(ignoreSelector) + } + } + /** Model configuration object or model name string (e.g., 'openai/gpt-5-nano') */ fun model(model: Model) = model(JsonField.of(model)) @@ -784,16 +846,32 @@ private constructor( * Further updates to this [Builder] will not mutate the returned instance. */ fun build(): Options = - Options(model, selector, timeout, additionalProperties.toMutableMap()) + Options( + (ignoreSelectors ?: JsonMissing.of()).map { it.toImmutable() }, + model, + selector, + timeout, + additionalProperties.toMutableMap(), + ) } private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Options = apply { if (validated) { return@apply } + ignoreSelectors() model().ifPresent { it.validate() } selector() timeout() @@ -816,7 +894,8 @@ private constructor( */ @JvmSynthetic internal fun validity(): Int = - (model.asKnown().getOrNull()?.validity() ?: 0) + + (ignoreSelectors.asKnown().getOrNull()?.size ?: 0) + + (model.asKnown().getOrNull()?.validity() ?: 0) + (if (selector.asKnown().isPresent) 1 else 0) + (if (timeout.asKnown().isPresent) 1 else 0) @@ -844,6 +923,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = model.accept(new Model.Visitor>() { + * @Override + * public Optional visitConfig(ModelConfig config) { + * return Optional.of(config.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { config != null -> visitor.visitConfig(config) @@ -853,6 +962,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Model = apply { if (validated) { return@apply @@ -998,6 +1117,7 @@ private constructor( } return other is Options && + ignoreSelectors == other.ignoreSelectors && model == other.model && selector == other.selector && timeout == other.timeout && @@ -1005,13 +1125,13 @@ private constructor( } private val hashCode: Int by lazy { - Objects.hash(model, selector, timeout, additionalProperties) + Objects.hash(ignoreSelectors, model, selector, timeout, additionalProperties) } override fun hashCode(): Int = hashCode override fun toString() = - "Options{model=$model, selector=$selector, timeout=$timeout, additionalProperties=$additionalProperties}" + "Options{ignoreSelectors=$ignoreSelectors, model=$model, selector=$selector, timeout=$timeout, additionalProperties=$additionalProperties}" } /** JSON Schema defining the structure of data to extract */ @@ -1073,6 +1193,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Schema = apply { if (validated) { return@apply @@ -1208,6 +1337,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractResponse.kt index 306abcd..e029256 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionExtractResponse.kt @@ -162,6 +162,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionExtractResponse = apply { if (validated) { return@apply @@ -319,6 +327,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt index d848600..7abd8a2 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateParams.kt @@ -589,6 +589,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -810,6 +819,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Options = apply { if (validated) { return@apply @@ -941,6 +959,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): WaitUntil = apply { if (validated) { return@apply @@ -1095,6 +1123,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateResponse.kt index 1f6e688..a4ccaa6 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionNavigateResponse.kt @@ -162,6 +162,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionNavigateResponse = apply { if (validated) { return@apply @@ -319,6 +327,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt index d18eb4a..8331e8b 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveParams.kt @@ -508,6 +508,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -771,6 +780,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Options = apply { if (validated) { return@apply @@ -828,6 +846,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = model.accept(new Model.Visitor>() { + * @Override + * public Optional visitConfig(ModelConfig config) { + * return Optional.of(config.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { config != null -> visitor.visitConfig(config) @@ -837,6 +885,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Model = apply { if (validated) { return@apply @@ -1042,6 +1100,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Variables = apply { if (validated) { return@apply @@ -1200,6 +1268,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveResponse.kt index 9c1c551..c28a2b3 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionObserveResponse.kt @@ -164,6 +164,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionObserveResponse = apply { if (validated) { return@apply @@ -352,6 +360,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply @@ -670,6 +687,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Result = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayParams.kt index 093a776..2d25194 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayParams.kt @@ -297,6 +297,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayResponse.kt index 5ac9a0d..a063510 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionReplayResponse.kt @@ -164,6 +164,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionReplayResponse = apply { if (validated) { return@apply @@ -355,6 +363,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply @@ -623,6 +640,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Page = apply { if (validated) { return@apply @@ -957,6 +984,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Action = apply { if (validated) { return@apply @@ -1058,6 +1095,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Parameters = apply { if (validated) { return@apply @@ -1165,6 +1212,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Result = apply { if (validated) { return@apply @@ -1424,6 +1481,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): TokenUsage = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt index 5e09071..7d6d521 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartParams.kt @@ -1080,6 +1080,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Body = apply { if (validated) { return@apply @@ -1336,6 +1345,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Browser = apply { if (validated) { return@apply @@ -2180,6 +2198,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): LaunchOptions = apply { if (validated) { return@apply @@ -2306,6 +2334,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): CdpHeaders = apply { if (validated) { return@apply @@ -2372,6 +2410,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the + * API, unless [visitor] overrides [Visitor.unknown]. To handle variants not known + * to this version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = ignoreDefaultArgs.accept(new IgnoreDefaultArgs.Visitor>() { + * @Override + * public Optional visitBool(Boolean bool) { + * return Optional.of(bool.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { bool != null -> visitor.visitBool(bool) @@ -2381,6 +2449,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): IgnoreDefaultArgs = apply { if (validated) { return@apply @@ -2746,6 +2824,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Proxy = apply { if (validated) { return@apply @@ -2957,6 +3045,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Viewport = apply { if (validated) { return@apply @@ -3158,6 +3256,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Type = apply { if (validated) { return@apply @@ -3284,9 +3392,13 @@ private constructor( fun keepAlive(): Optional = keepAlive.getOptional("keepAlive") /** + * Deprecated. Browserbase API keys are now project-scoped, so this field is no longer + * required. + * * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if * the server responded with an unexpected value). */ + @Deprecated("deprecated") fun projectId(): Optional = projectId.getOptional("projectId") /** @@ -3344,7 +3456,10 @@ private constructor( * * Unlike [projectId], this method doesn't throw if the JSON field has an unexpected type. */ - @JsonProperty("projectId") @ExcludeMissing fun _projectId(): JsonField = projectId + @Deprecated("deprecated") + @JsonProperty("projectId") + @ExcludeMissing + fun _projectId(): JsonField = projectId /** * Returns the raw JSON value of [proxies]. @@ -3464,6 +3579,11 @@ private constructor( */ fun keepAlive(keepAlive: JsonField) = apply { this.keepAlive = keepAlive } + /** + * Deprecated. Browserbase API keys are now project-scoped, so this field is no longer + * required. + */ + @Deprecated("deprecated") fun projectId(projectId: String) = projectId(JsonField.of(projectId)) /** @@ -3473,6 +3593,7 @@ private constructor( * This method is primarily for setting the field to an undocumented or not yet * supported value. */ + @Deprecated("deprecated") fun projectId(projectId: JsonField) = apply { this.projectId = projectId } fun proxies(proxies: Proxies) = proxies(JsonField.of(proxies)) @@ -3568,6 +3689,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): BrowserbaseSessionCreateParams = apply { if (validated) { return@apply @@ -3614,12 +3744,16 @@ private constructor( private constructor( private val advancedStealth: JsonField, private val blockAds: JsonField, + private val captchaImageSelector: JsonField, + private val captchaInputSelector: JsonField, private val context: JsonField, private val extensionId: JsonField, private val fingerprint: JsonField, private val logSession: JsonField, + private val os: JsonField, private val recordSession: JsonField, private val solveCaptchas: JsonField, + private val verified: JsonField, private val viewport: JsonField, private val additionalProperties: MutableMap, ) { @@ -3632,6 +3766,12 @@ private constructor( @JsonProperty("blockAds") @ExcludeMissing blockAds: JsonField = JsonMissing.of(), + @JsonProperty("captchaImageSelector") + @ExcludeMissing + captchaImageSelector: JsonField = JsonMissing.of(), + @JsonProperty("captchaInputSelector") + @ExcludeMissing + captchaInputSelector: JsonField = JsonMissing.of(), @JsonProperty("context") @ExcludeMissing context: JsonField = JsonMissing.of(), @@ -3644,24 +3784,32 @@ private constructor( @JsonProperty("logSession") @ExcludeMissing logSession: JsonField = JsonMissing.of(), + @JsonProperty("os") @ExcludeMissing os: JsonField = JsonMissing.of(), @JsonProperty("recordSession") @ExcludeMissing recordSession: JsonField = JsonMissing.of(), @JsonProperty("solveCaptchas") @ExcludeMissing solveCaptchas: JsonField = JsonMissing.of(), + @JsonProperty("verified") + @ExcludeMissing + verified: JsonField = JsonMissing.of(), @JsonProperty("viewport") @ExcludeMissing viewport: JsonField = JsonMissing.of(), ) : this( advancedStealth, blockAds, + captchaImageSelector, + captchaInputSelector, context, extensionId, fingerprint, logSession, + os, recordSession, solveCaptchas, + verified, viewport, mutableMapOf(), ) @@ -3679,6 +3827,20 @@ private constructor( */ fun blockAds(): Optional = blockAds.getOptional("blockAds") + /** + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. + * if the server responded with an unexpected value). + */ + fun captchaImageSelector(): Optional = + captchaImageSelector.getOptional("captchaImageSelector") + + /** + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. + * if the server responded with an unexpected value). + */ + fun captchaInputSelector(): Optional = + captchaInputSelector.getOptional("captchaInputSelector") + /** * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. * if the server responded with an unexpected value). @@ -3703,6 +3865,12 @@ private constructor( */ fun logSession(): Optional = logSession.getOptional("logSession") + /** + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. + * if the server responded with an unexpected value). + */ + fun os(): Optional = os.getOptional("os") + /** * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. * if the server responded with an unexpected value). @@ -3715,6 +3883,12 @@ private constructor( */ fun solveCaptchas(): Optional = solveCaptchas.getOptional("solveCaptchas") + /** + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. + * if the server responded with an unexpected value). + */ + fun verified(): Optional = verified.getOptional("verified") + /** * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. * if the server responded with an unexpected value). @@ -3739,6 +3913,26 @@ private constructor( */ @JsonProperty("blockAds") @ExcludeMissing fun _blockAds(): JsonField = blockAds + /** + * Returns the raw JSON value of [captchaImageSelector]. + * + * Unlike [captchaImageSelector], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("captchaImageSelector") + @ExcludeMissing + fun _captchaImageSelector(): JsonField = captchaImageSelector + + /** + * Returns the raw JSON value of [captchaInputSelector]. + * + * Unlike [captchaInputSelector], this method doesn't throw if the JSON field has an + * unexpected type. + */ + @JsonProperty("captchaInputSelector") + @ExcludeMissing + fun _captchaInputSelector(): JsonField = captchaInputSelector + /** * Returns the raw JSON value of [context]. * @@ -3776,6 +3970,13 @@ private constructor( @ExcludeMissing fun _logSession(): JsonField = logSession + /** + * Returns the raw JSON value of [os]. + * + * Unlike [os], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("os") @ExcludeMissing fun _os(): JsonField = os + /** * Returns the raw JSON value of [recordSession]. * @@ -3796,6 +3997,14 @@ private constructor( @ExcludeMissing fun _solveCaptchas(): JsonField = solveCaptchas + /** + * Returns the raw JSON value of [verified]. + * + * Unlike [verified], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("verified") @ExcludeMissing fun _verified(): JsonField = verified + /** * Returns the raw JSON value of [viewport]. * @@ -3829,12 +4038,16 @@ private constructor( private var advancedStealth: JsonField = JsonMissing.of() private var blockAds: JsonField = JsonMissing.of() + private var captchaImageSelector: JsonField = JsonMissing.of() + private var captchaInputSelector: JsonField = JsonMissing.of() private var context: JsonField = JsonMissing.of() private var extensionId: JsonField = JsonMissing.of() private var fingerprint: JsonField = JsonMissing.of() private var logSession: JsonField = JsonMissing.of() + private var os: JsonField = JsonMissing.of() private var recordSession: JsonField = JsonMissing.of() private var solveCaptchas: JsonField = JsonMissing.of() + private var verified: JsonField = JsonMissing.of() private var viewport: JsonField = JsonMissing.of() private var additionalProperties: MutableMap = mutableMapOf() @@ -3842,12 +4055,16 @@ private constructor( internal fun from(browserSettings: BrowserSettings) = apply { advancedStealth = browserSettings.advancedStealth blockAds = browserSettings.blockAds + captchaImageSelector = browserSettings.captchaImageSelector + captchaInputSelector = browserSettings.captchaInputSelector context = browserSettings.context extensionId = browserSettings.extensionId fingerprint = browserSettings.fingerprint logSession = browserSettings.logSession + os = browserSettings.os recordSession = browserSettings.recordSession solveCaptchas = browserSettings.solveCaptchas + verified = browserSettings.verified viewport = browserSettings.viewport additionalProperties = browserSettings.additionalProperties.toMutableMap() } @@ -3877,6 +4094,34 @@ private constructor( */ fun blockAds(blockAds: JsonField) = apply { this.blockAds = blockAds } + fun captchaImageSelector(captchaImageSelector: String) = + captchaImageSelector(JsonField.of(captchaImageSelector)) + + /** + * Sets [Builder.captchaImageSelector] to an arbitrary JSON value. + * + * You should usually call [Builder.captchaImageSelector] with a well-typed [String] + * value instead. This method is primarily for setting the field to an undocumented + * or not yet supported value. + */ + fun captchaImageSelector(captchaImageSelector: JsonField) = apply { + this.captchaImageSelector = captchaImageSelector + } + + fun captchaInputSelector(captchaInputSelector: String) = + captchaInputSelector(JsonField.of(captchaInputSelector)) + + /** + * Sets [Builder.captchaInputSelector] to an arbitrary JSON value. + * + * You should usually call [Builder.captchaInputSelector] with a well-typed [String] + * value instead. This method is primarily for setting the field to an undocumented + * or not yet supported value. + */ + fun captchaInputSelector(captchaInputSelector: JsonField) = apply { + this.captchaInputSelector = captchaInputSelector + } + fun context(context: Context) = context(JsonField.of(context)) /** @@ -3927,6 +4172,17 @@ private constructor( this.logSession = logSession } + fun os(os: Os) = os(JsonField.of(os)) + + /** + * Sets [Builder.os] to an arbitrary JSON value. + * + * You should usually call [Builder.os] with a well-typed [Os] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun os(os: JsonField) = apply { this.os = os } + fun recordSession(recordSession: Boolean) = recordSession(JsonField.of(recordSession)) @@ -3955,6 +4211,17 @@ private constructor( this.solveCaptchas = solveCaptchas } + fun verified(verified: Boolean) = verified(JsonField.of(verified)) + + /** + * Sets [Builder.verified] to an arbitrary JSON value. + * + * You should usually call [Builder.verified] with a well-typed [Boolean] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun verified(verified: JsonField) = apply { this.verified = verified } + fun viewport(viewport: Viewport) = viewport(JsonField.of(viewport)) /** @@ -3997,12 +4264,16 @@ private constructor( BrowserSettings( advancedStealth, blockAds, + captchaImageSelector, + captchaInputSelector, context, extensionId, fingerprint, logSession, + os, recordSession, solveCaptchas, + verified, viewport, additionalProperties.toMutableMap(), ) @@ -4010,6 +4281,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): BrowserSettings = apply { if (validated) { return@apply @@ -4017,12 +4298,16 @@ private constructor( advancedStealth() blockAds() + captchaImageSelector() + captchaInputSelector() context().ifPresent { it.validate() } extensionId() fingerprint().ifPresent { it.validate() } logSession() + os().ifPresent { it.validate() } recordSession() solveCaptchas() + verified() viewport().ifPresent { it.validate() } validated = true } @@ -4045,12 +4330,16 @@ private constructor( internal fun validity(): Int = (if (advancedStealth.asKnown().isPresent) 1 else 0) + (if (blockAds.asKnown().isPresent) 1 else 0) + + (if (captchaImageSelector.asKnown().isPresent) 1 else 0) + + (if (captchaInputSelector.asKnown().isPresent) 1 else 0) + (context.asKnown().getOrNull()?.validity() ?: 0) + (if (extensionId.asKnown().isPresent) 1 else 0) + (fingerprint.asKnown().getOrNull()?.validity() ?: 0) + (if (logSession.asKnown().isPresent) 1 else 0) + + (os.asKnown().getOrNull()?.validity() ?: 0) + (if (recordSession.asKnown().isPresent) 1 else 0) + (if (solveCaptchas.asKnown().isPresent) 1 else 0) + + (if (verified.asKnown().isPresent) 1 else 0) + (viewport.asKnown().getOrNull()?.validity() ?: 0) class Context @@ -4204,6 +4493,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Context = apply { if (validated) { return@apply @@ -4599,6 +4898,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Fingerprint = apply { if (validated) { return@apply @@ -4744,6 +5053,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Browser = apply { if (validated) { return@apply @@ -4878,6 +5197,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Device = apply { if (validated) { return@apply @@ -5015,6 +5344,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): HttpVersion = apply { if (validated) { return@apply @@ -5172,6 +5511,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): OperatingSystem = apply { if (validated) { return@apply @@ -5429,6 +5778,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Screen = apply { if (validated) { return@apply @@ -5518,6 +5877,163 @@ private constructor( "Fingerprint{browsers=$browsers, devices=$devices, httpVersion=$httpVersion, locales=$locales, operatingSystems=$operatingSystems, screen=$screen, additionalProperties=$additionalProperties}" } + class Os @JsonCreator private constructor(private val value: JsonField) : Enum { + + /** + * Returns this class instance's raw value. + * + * This is usually only useful if this instance was deserialized from data that + * doesn't match any known member, and you want to know that value. For example, if + * the SDK is on an older version than the API, then the API may respond with new + * members that the SDK is unaware of. + */ + @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value + + companion object { + + @JvmField val WINDOWS = of("windows") + + @JvmField val MAC = of("mac") + + @JvmField val LINUX = of("linux") + + @JvmField val MOBILE = of("mobile") + + @JvmField val TABLET = of("tablet") + + @JvmStatic fun of(value: String) = Os(JsonField.of(value)) + } + + /** An enum containing [Os]'s known values. */ + enum class Known { + WINDOWS, + MAC, + LINUX, + MOBILE, + TABLET, + } + + /** + * An enum containing [Os]'s known values, as well as an [_UNKNOWN] member. + * + * An instance of [Os] can contain an unknown value in a couple of cases: + * - It was deserialized from data that doesn't match any known member. For example, + * if the SDK is on an older version than the API, then the API may respond with + * new members that the SDK is unaware of. + * - It was constructed with an arbitrary value using the [of] method. + */ + enum class Value { + WINDOWS, + MAC, + LINUX, + MOBILE, + TABLET, + /** + * An enum member indicating that [Os] was instantiated with an unknown value. + */ + _UNKNOWN, + } + + /** + * Returns an enum member corresponding to this class instance's value, or + * [Value._UNKNOWN] if the class was instantiated with an unknown value. + * + * Use the [known] method instead if you're certain the value is always known or if + * you want to throw for the unknown case. + */ + fun value(): Value = + when (this) { + WINDOWS -> Value.WINDOWS + MAC -> Value.MAC + LINUX -> Value.LINUX + MOBILE -> Value.MOBILE + TABLET -> Value.TABLET + else -> Value._UNKNOWN + } + + /** + * Returns an enum member corresponding to this class instance's value. + * + * Use the [value] method instead if you're uncertain the value is always known and + * don't want to throw for the unknown case. + * + * @throws StagehandInvalidDataException if this class instance's value is a not a + * known member. + */ + fun known(): Known = + when (this) { + WINDOWS -> Known.WINDOWS + MAC -> Known.MAC + LINUX -> Known.LINUX + MOBILE -> Known.MOBILE + TABLET -> Known.TABLET + else -> throw StagehandInvalidDataException("Unknown Os: $value") + } + + /** + * Returns this class instance's primitive wire representation. + * + * This differs from the [toString] method because that method is primarily for + * debugging and generally doesn't throw. + * + * @throws StagehandInvalidDataException if this class instance's value does not + * have the expected primitive type. + */ + fun asString(): String = + _value().asString().orElseThrow { + StagehandInvalidDataException("Value is not a String") + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ + fun validate(): Os = apply { + if (validated) { + return@apply + } + + known() + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: StagehandInvalidDataException) { + false + } + + /** + * Returns a score indicating how many valid values are contained in this object + * recursively. + * + * Used for best match union deserialization. + */ + @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Os && value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() + } + class Viewport @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( @@ -5651,6 +6167,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Viewport = apply { if (validated) { return@apply @@ -5709,12 +6235,16 @@ private constructor( return other is BrowserSettings && advancedStealth == other.advancedStealth && blockAds == other.blockAds && + captchaImageSelector == other.captchaImageSelector && + captchaInputSelector == other.captchaInputSelector && context == other.context && extensionId == other.extensionId && fingerprint == other.fingerprint && logSession == other.logSession && + os == other.os && recordSession == other.recordSession && solveCaptchas == other.solveCaptchas && + verified == other.verified && viewport == other.viewport && additionalProperties == other.additionalProperties } @@ -5723,12 +6253,16 @@ private constructor( Objects.hash( advancedStealth, blockAds, + captchaImageSelector, + captchaInputSelector, context, extensionId, fingerprint, logSession, + os, recordSession, solveCaptchas, + verified, viewport, additionalProperties, ) @@ -5737,7 +6271,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "BrowserSettings{advancedStealth=$advancedStealth, blockAds=$blockAds, context=$context, extensionId=$extensionId, fingerprint=$fingerprint, logSession=$logSession, recordSession=$recordSession, solveCaptchas=$solveCaptchas, viewport=$viewport, additionalProperties=$additionalProperties}" + "BrowserSettings{advancedStealth=$advancedStealth, blockAds=$blockAds, captchaImageSelector=$captchaImageSelector, captchaInputSelector=$captchaInputSelector, context=$context, extensionId=$extensionId, fingerprint=$fingerprint, logSession=$logSession, os=$os, recordSession=$recordSession, solveCaptchas=$solveCaptchas, verified=$verified, viewport=$viewport, additionalProperties=$additionalProperties}" } @JsonDeserialize(using = Proxies.Deserializer::class) @@ -5765,6 +6299,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, + * unless [visitor] overrides [Visitor.unknown]. To handle variants not known to this + * version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = proxies.accept(new Proxies.Visitor>() { + * @Override + * public Optional visitBool(Boolean bool) { + * return Optional.of(bool.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { bool != null -> visitor.visitBool(bool) @@ -5774,6 +6338,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Proxies = apply { if (validated) { return@apply @@ -5942,6 +6516,36 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given + * [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the + * API, unless [visitor] overrides [Visitor.unknown]. To handle variants not known + * to this version of the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = proxyConfig.accept(new ProxyConfig.Visitor>() { + * @Override + * public Optional visitBrowserbase(Browserbase browserbase) { + * return Optional.of(browserbase.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in + * [visitor] and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { browserbase != null -> visitor.visitBrowserbase(browserbase) @@ -5951,6 +6555,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): ProxyConfig = apply { if (validated) { return@apply @@ -6272,6 +6886,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Browserbase = apply { if (validated) { return@apply @@ -6503,6 +7127,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their + * expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): Geolocation = apply { if (validated) { return@apply @@ -6841,6 +7475,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected + * types recursively. + * + * This method is _not_ forwards compatible with new types from the API for + * existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object + * doesn't match its expected type. + */ fun validate(): External = apply { if (validated) { return@apply @@ -7018,6 +7662,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): Region = apply { if (validated) { return@apply @@ -7117,6 +7771,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): UserMetadata = apply { if (validated) { return@apply @@ -7287,6 +7951,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Verbose = apply { if (validated) { return@apply @@ -7419,6 +8092,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): XStreamResponse = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartResponse.kt index e342e19..423a237 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartResponse.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionStartResponse.kt @@ -162,6 +162,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): SessionStartResponse = apply { if (validated) { return@apply @@ -379,6 +387,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/StreamEvent.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/StreamEvent.kt index 4584374..3c0b838 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/StreamEvent.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/StreamEvent.kt @@ -225,6 +225,14 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): StreamEvent = apply { if (validated) { return@apply @@ -282,6 +290,35 @@ private constructor( fun _json(): Optional = Optional.ofNullable(_json) + /** + * Maps this instance's current variant to a value of type [T] using the given [visitor]. + * + * Note that this method is _not_ forwards compatible with new variants from the API, unless + * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of + * the SDK gracefully, consider overriding [Visitor.unknown]: + * ```java + * import com.browserbase.api.core.JsonValue; + * import java.util.Optional; + * + * Optional result = data.accept(new Data.Visitor>() { + * @Override + * public Optional visitStreamEventSystemDataOutput(StreamEventSystemDataOutput streamEventSystemDataOutput) { + * return Optional.of(streamEventSystemDataOutput.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in [visitor] + * and the current variant is unknown. + */ fun accept(visitor: Visitor): T = when { streamEventSystemDataOutput != null -> @@ -293,6 +330,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Data = apply { if (validated) { return@apply @@ -627,6 +673,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): StreamEventSystemDataOutput = apply { if (validated) { return@apply @@ -768,6 +824,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't + * match its expected type. + */ fun validate(): Status = apply { if (validated) { return@apply @@ -982,6 +1048,16 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing + * fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match + * its expected type. + */ fun validate(): StreamEventLogDataOutput = apply { if (validated) { return@apply @@ -1127,6 +1203,15 @@ private constructor( private var validated: Boolean = false + /** + * Validates that the types of all values in this object match their expected types + * recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws StagehandInvalidDataException if any value type in this object doesn't match its + * expected type. + */ fun validate(): Type = apply { if (validated) { return@apply diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ClientOptionsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ClientOptionsTest.kt index 1bd88a6..ad9a044 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ClientOptionsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/ClientOptionsTest.kt @@ -47,6 +47,53 @@ internal class ClientOptionsTest { .containsExactly("another My Browserbase API Key") } + @Test + fun browserbaseProjectId_isNoOp() { + val clientOptions = + ClientOptions.builder() + .httpClient(httpClient) + .browserbaseApiKey("My Browserbase API Key") + .browserbaseProjectId("My Browserbase Project ID") + .modelApiKey("My Model API Key") + .build() + + assertThat(clientOptions.headers.values("x-bb-project-id")).isEmpty() + } + + @Test + fun fromEnv_prefersStagehandApiUrl() { + val env = + mapOf( + "STAGEHAND_API_URL" to "https://example.com/from-api-env", + "STAGEHAND_BASE_URL" to "https://example.com/from-base-env", + ) + val clientOptions = + ClientOptions.builder() + .httpClient(httpClient) + .browserbaseApiKey("My Browserbase API Key") + .browserbaseProjectId("My Browserbase Project ID") + .modelApiKey("My Model API Key") + .fromEnv(env::get) + .build() + + assertThat(clientOptions.baseUrl()).isEqualTo("https://example.com/from-api-env") + } + + @Test + fun fromEnv_usesLegacyStagehandBaseUrl() { + val env = mapOf("STAGEHAND_BASE_URL" to "https://example.com/from-base-env") + val clientOptions = + ClientOptions.builder() + .httpClient(httpClient) + .browserbaseApiKey("My Browserbase API Key") + .browserbaseProjectId("My Browserbase Project ID") + .modelApiKey("My Model API Key") + .fromEnv(env::get) + .build() + + assertThat(clientOptions.baseUrl()).isEqualTo("https://example.com/from-base-env") + } + @Test fun toBuilder_whenOriginalClientOptionsGarbageCollected_doesNotCloseOriginalClient() { var clientOptions = diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt index f040cad..da74169 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExecuteParamsTest.kt @@ -57,6 +57,11 @@ internal class SessionExecuteParamsTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -133,6 +138,11 @@ internal class SessionExecuteParamsTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -213,6 +223,11 @@ internal class SessionExecuteParamsTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -266,6 +281,11 @@ internal class SessionExecuteParamsTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) assertThat(body.frameId()).contains("frameId") diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt index d84a3f9..30d7652 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionExtractParamsTest.kt @@ -18,6 +18,7 @@ internal class SessionExtractParamsTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -63,6 +64,7 @@ internal class SessionExtractParamsTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -112,6 +114,7 @@ internal class SessionExtractParamsTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -144,6 +147,7 @@ internal class SessionExtractParamsTest { assertThat(body.options()) .contains( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt index 7ebe44e..0a8dffc 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionStartParamsTest.kt @@ -67,6 +67,8 @@ internal class SessionStartParamsTest { SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings.builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings .Context @@ -124,8 +126,13 @@ internal class SessionStartParamsTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings.Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings .Viewport @@ -219,6 +226,8 @@ internal class SessionStartParamsTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -278,8 +287,15 @@ internal class SessionStartParamsTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -387,6 +403,8 @@ internal class SessionStartParamsTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -446,8 +464,15 @@ internal class SessionStartParamsTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -539,6 +564,8 @@ internal class SessionStartParamsTest { SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings.builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings .Context @@ -596,8 +623,13 @@ internal class SessionStartParamsTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings.Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams.BrowserSettings .Viewport diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt index a3bc8a0..6941c36 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ErrorHandlingTest.kt @@ -136,6 +136,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -200,8 +202,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -322,6 +331,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -386,8 +397,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -508,6 +526,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -572,8 +592,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -694,6 +721,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -758,8 +787,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -880,6 +916,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -944,8 +982,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1066,6 +1111,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1130,8 +1177,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1252,6 +1306,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1316,8 +1372,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1438,6 +1501,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1502,8 +1567,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1624,6 +1696,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1688,8 +1762,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1810,6 +1891,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1874,8 +1957,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -1996,6 +2086,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2060,8 +2152,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2182,6 +2281,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2246,8 +2347,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2368,6 +2476,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2432,8 +2542,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2554,6 +2671,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2618,8 +2737,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2740,6 +2866,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2804,8 +2932,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2926,6 +3061,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -2990,8 +3127,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -3110,6 +3254,8 @@ internal class ErrorHandlingTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -3174,8 +3320,15 @@ internal class ErrorHandlingTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt index f423bd2..392cc05 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/ServiceParamsTest.kt @@ -104,6 +104,8 @@ internal class ServiceParamsTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -163,8 +165,15 @@ internal class ServiceParamsTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt index 3635e44..8d708e4 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/async/SessionServiceAsyncTest.kt @@ -215,6 +215,11 @@ internal class SessionServiceAsyncTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -285,6 +290,11 @@ internal class SessionServiceAsyncTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -317,6 +327,7 @@ internal class SessionServiceAsyncTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -366,6 +377,7 @@ internal class SessionServiceAsyncTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -640,6 +652,8 @@ internal class SessionServiceAsyncTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -699,8 +713,15 @@ internal class SessionServiceAsyncTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt index 15ae6c4..7ede1f2 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/services/blocking/SessionServiceTest.kt @@ -213,6 +213,11 @@ internal class SessionServiceTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -282,6 +287,11 @@ internal class SessionServiceTest { .maxSteps(20.0) .toolTimeout(30000.0) .useSearch(true) + .variables( + SessionExecuteParams.ExecuteOptions.Variables.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .build() ) .frameId("frameId") @@ -314,6 +324,7 @@ internal class SessionServiceTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -362,6 +373,7 @@ internal class SessionServiceTest { .instruction("Extract all product names and prices from the page") .options( SessionExtractParams.Options.builder() + .ignoreSelectors(listOf("nav", ".cookie-banner", "#sidebar-ads")) .model( ModelConfig.builder() .modelName("openai/gpt-5.4-mini") @@ -633,6 +645,8 @@ internal class SessionServiceTest { .builder() .advancedStealth(true) .blockAds(true) + .captchaImageSelector("captchaImageSelector") + .captchaInputSelector("captchaInputSelector") .context( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings @@ -692,8 +706,15 @@ internal class SessionServiceTest { .build() ) .logSession(true) + .os( + SessionStartParams.BrowserbaseSessionCreateParams + .BrowserSettings + .Os + .WINDOWS + ) .recordSession(true) .solveCaptchas(true) + .verified(true) .viewport( SessionStartParams.BrowserbaseSessionCreateParams .BrowserSettings diff --git a/stagehand-java-example/src/main/java/com/stagehand/api/example/Env.java b/stagehand-java-example/src/main/java/com/stagehand/api/example/Env.java index 88003c4..aeedd92 100644 --- a/stagehand-java-example/src/main/java/com/stagehand/api/example/Env.java +++ b/stagehand-java-example/src/main/java/com/stagehand/api/example/Env.java @@ -7,7 +7,7 @@ final class Env { private static final String[] REQUIRED_KEYS = { - "STAGEHAND_API_URL", "MODEL_API_KEY", "BROWSERBASE_API_KEY", "BROWSERBASE_PROJECT_ID", + "MODEL_API_KEY", "BROWSERBASE_API_KEY", }; private Env() {} @@ -34,15 +34,17 @@ static void load() { case "BROWSERBASE_API_KEY": System.setProperty("stagehand.browserbaseApiKey", value); break; - case "BROWSERBASE_PROJECT_ID": - System.setProperty("stagehand.browserbaseProjectId", value); - break; case "MODEL_API_KEY": System.setProperty("stagehand.modelApiKey", value); break; case "STAGEHAND_API_URL": System.setProperty("stagehand.baseUrl", value); break; + case "STAGEHAND_BASE_URL": + if (System.getProperty("stagehand.baseUrl") == null) { + System.setProperty("stagehand.baseUrl", value); + } + break; default: break; } @@ -67,8 +69,6 @@ private static String mapKey(String key) { switch (key) { case "BROWSERBASE_API_KEY": return "stagehand.browserbaseApiKey"; - case "BROWSERBASE_PROJECT_ID": - return "stagehand.browserbaseProjectId"; case "MODEL_API_KEY": return "stagehand.modelApiKey"; case "STAGEHAND_API_URL": diff --git a/stagehand-java-example/src/main/java/com/stagehand/api/example/LocalServerMultiregionBrowserExample.java b/stagehand-java-example/src/main/java/com/stagehand/api/example/LocalServerMultiregionBrowserExample.java index a647507..16fd19b 100644 --- a/stagehand-java-example/src/main/java/com/stagehand/api/example/LocalServerMultiregionBrowserExample.java +++ b/stagehand-java-example/src/main/java/com/stagehand/api/example/LocalServerMultiregionBrowserExample.java @@ -33,9 +33,8 @@ * * Required environment variables: * - BROWSERBASE_API_KEY: Your Browserbase API key - * - BROWSERBASE_PROJECT_ID: Your Browserbase project ID * - MODEL_API_KEY: Your OpenAI API key - * - STAGEHAND_API_URL: Local Stagehand server base URL + * Optional: STAGEHAND_API_URL or STAGEHAND_BASE_URL for a local Stagehand server base URL */ public class LocalServerMultiregionBrowserExample { public static void main(String[] args) { @@ -49,7 +48,6 @@ public static void main(String[] args) { .type(SessionStartParams.Browser.Type.BROWSERBASE) .build()) .browserbaseSessionCreateParams(SessionStartParams.BrowserbaseSessionCreateParams.builder() - .projectId(System.getProperty("stagehand.browserbaseProjectId")) .region(SessionStartParams.BrowserbaseSessionCreateParams.Region.EU_CENTRAL_1) .build()) .build()); diff --git a/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java b/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java index 73e8cc2..29692cd 100644 --- a/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java +++ b/stagehand-java-example/src/main/java/com/stagehand/api/example/Main.java @@ -24,14 +24,13 @@ * * Required environment variables: * - BROWSERBASE_API_KEY: Your Browserbase API key - * - BROWSERBASE_PROJECT_ID: Your Browserbase project ID * - MODEL_API_KEY: Your OpenAI API key */ public class Main { public static void main(String[] args) { Env.load(); // Create client using environment variables - // BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID, MODEL_API_KEY + // BROWSERBASE_API_KEY and MODEL_API_KEY StagehandClient client = StagehandOkHttpClient.builder().fromEnv().build(); // Start a new browser session diff --git a/stagehand-java-example/src/main/java/com/stagehand/api/example/RemoteBrowserPlaywrightExample.java b/stagehand-java-example/src/main/java/com/stagehand/api/example/RemoteBrowserPlaywrightExample.java index bfb4c36..bfc9bc6 100644 --- a/stagehand-java-example/src/main/java/com/stagehand/api/example/RemoteBrowserPlaywrightExample.java +++ b/stagehand-java-example/src/main/java/com/stagehand/api/example/RemoteBrowserPlaywrightExample.java @@ -33,14 +33,13 @@ * * Required environment variables: * - BROWSERBASE_API_KEY: Your Browserbase API key - * - BROWSERBASE_PROJECT_ID: Your Browserbase project ID * - MODEL_API_KEY: Your OpenAI API key */ public class RemoteBrowserPlaywrightExample { public static void main(String[] args) { Env.load(); // Create client using environment variables - // BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID, MODEL_API_KEY + // BROWSERBASE_API_KEY and MODEL_API_KEY StagehandClient client = StagehandOkHttpClient.builder().fromEnv().build(); // Start a new remote Browserbase session (Playwright) @@ -50,9 +49,6 @@ public static void main(String[] args) { .browser(SessionStartParams.Browser.builder() .type(SessionStartParams.Browser.Type.BROWSERBASE) .build()) - .browserbaseSessionCreateParams(SessionStartParams.BrowserbaseSessionCreateParams.builder() - .projectId(System.getProperty("stagehand.browserbaseProjectId")) - .build()) .build()); String sessionId = startResponse.data().sessionId();