From f94c8c9a86af4592fba5c42645ccc1099a8ab1fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:40:33 +0000 Subject: [PATCH 1/7] chore(internal): codegen related update --- .../com/browserbase/api/core/http/RetryingHttpClient.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt index ab5bd0b..5ddfb06 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt @@ -214,13 +214,8 @@ private constructor( } } ?.let { retryAfterNanos -> - // If the API asks us to wait a certain amount of time (and it's a reasonable - // amount), just - // do what it says. - val retryAfter = Duration.ofNanos(retryAfterNanos.toLong()) - if (retryAfter in Duration.ofNanos(0)..Duration.ofMinutes(1)) { - return retryAfter - } + // If the API asks us to wait a certain amount of time, do what it says. + return Duration.ofNanos(retryAfterNanos.toLong()) } // Apply exponential backoff, but not more than the max. From 3bf036aed5ba8fb90fe4bf5bc6b62a49f09f7dcc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 08:43:37 +0000 Subject: [PATCH 2/7] chore(internal): bump palantir-java-format --- buildSrc/src/main/kotlin/stagehand.java.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/stagehand.java.gradle.kts b/buildSrc/src/main/kotlin/stagehand.java.gradle.kts index 70fc33f..8f4f902 100644 --- a/buildSrc/src/main/kotlin/stagehand.java.gradle.kts +++ b/buildSrc/src/main/kotlin/stagehand.java.gradle.kts @@ -45,7 +45,7 @@ tasks.withType().configureEach { val palantir by configurations.creating dependencies { - palantir("com.palantir.javaformat:palantir-java-format:2.73.0") + palantir("com.palantir.javaformat:palantir-java-format:2.89.0") } fun registerPalantir( From 4a1399c7a535d9bf8934e9d142878e57a1537b8d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:36:58 +0000 Subject: [PATCH 3/7] chore(ci): skip uploading artifacts on stainless-internal branches --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27fda9e..5eb58fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,14 +65,18 @@ jobs: run: ./scripts/build - name: Get GitHub OIDC Token - if: github.repository == 'stainless-sdks/stagehand-java' + if: |- + github.repository == 'stainless-sdks/stagehand-java' && + !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); - name: Build and upload Maven artifacts - if: github.repository == 'stainless-sdks/stagehand-java' + if: |- + github.repository == 'stainless-sdks/stagehand-java' && + !startsWith(github.ref, 'refs/heads/stl/') env: URL: https://pkg.stainless.com/s AUTH: ${{ steps.github-oidc.outputs.github_token }} From 5a9506d5dfeb1ae172834941aad3bbd9e96f2973 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:28:25 +0000 Subject: [PATCH 4/7] feat: Add missing cdpHeaders field to v3 server openapi spec --- .stats.yml | 4 +- .../api/models/sessions/SessionStartParams.kt | 147 ++++++++++++++++- .../models/sessions/SessionStartParamsTest.kt | 20 +++ .../api/services/ErrorHandlingTest.kt | 153 ++++++++++++++++++ .../api/services/ServiceParamsTest.kt | 5 + .../services/async/SessionServiceAsyncTest.kt | 6 + .../services/blocking/SessionServiceTest.kt | 6 + 7 files changed, 338 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index c61740e..a959f06 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-a4e672f457dd99336f4b2a113fd7c7c6c9db0941b38d57cff6e3641549a6c4ed.yml -openapi_spec_hash: eae9c8561e420db8e4d238c1e59617fb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-573d364768ac1902ee5ed8b2485d3b293bda0ea8ff7898aef1a3fd6be79b594a.yml +openapi_spec_hash: 107ec840f4330885dd2232a05a66fed7 config_hash: 2a565ad6662259a2e90fa5f1f5095525 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 f177a4e..5e09071 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 @@ -1372,6 +1372,7 @@ private constructor( private constructor( private val acceptDownloads: JsonField, private val args: JsonField>, + private val cdpHeaders: JsonField, private val cdpUrl: JsonField, private val chromiumSandbox: JsonField, private val connectTimeoutMs: JsonField, @@ -1400,6 +1401,9 @@ private constructor( @JsonProperty("args") @ExcludeMissing args: JsonField> = JsonMissing.of(), + @JsonProperty("cdpHeaders") + @ExcludeMissing + cdpHeaders: JsonField = JsonMissing.of(), @JsonProperty("cdpUrl") @ExcludeMissing cdpUrl: JsonField = JsonMissing.of(), @@ -1450,6 +1454,7 @@ private constructor( ) : this( acceptDownloads, args, + cdpHeaders, cdpUrl, chromiumSandbox, connectTimeoutMs, @@ -1483,6 +1488,12 @@ private constructor( */ fun args(): Optional> = args.getOptional("args") + /** + * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. + * if the server responded with an unexpected value). + */ + fun cdpHeaders(): Optional = cdpHeaders.getOptional("cdpHeaders") + /** * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. * if the server responded with an unexpected value). @@ -1608,6 +1619,16 @@ private constructor( */ @JsonProperty("args") @ExcludeMissing fun _args(): JsonField> = args + /** + * Returns the raw JSON value of [cdpHeaders]. + * + * Unlike [cdpHeaders], this method doesn't throw if the JSON field has an unexpected + * type. + */ + @JsonProperty("cdpHeaders") + @ExcludeMissing + fun _cdpHeaders(): JsonField = cdpHeaders + /** * Returns the raw JSON value of [cdpUrl]. * @@ -1783,6 +1804,7 @@ private constructor( private var acceptDownloads: JsonField = JsonMissing.of() private var args: JsonField>? = null + private var cdpHeaders: JsonField = JsonMissing.of() private var cdpUrl: JsonField = JsonMissing.of() private var chromiumSandbox: JsonField = JsonMissing.of() private var connectTimeoutMs: JsonField = JsonMissing.of() @@ -1806,6 +1828,7 @@ private constructor( internal fun from(launchOptions: LaunchOptions) = apply { acceptDownloads = launchOptions.acceptDownloads args = launchOptions.args.map { it.toMutableList() } + cdpHeaders = launchOptions.cdpHeaders cdpUrl = launchOptions.cdpUrl chromiumSandbox = launchOptions.chromiumSandbox connectTimeoutMs = launchOptions.connectTimeoutMs @@ -1865,6 +1888,19 @@ private constructor( } } + fun cdpHeaders(cdpHeaders: CdpHeaders) = cdpHeaders(JsonField.of(cdpHeaders)) + + /** + * Sets [Builder.cdpHeaders] to an arbitrary JSON value. + * + * You should usually call [Builder.cdpHeaders] with a well-typed [CdpHeaders] value + * instead. This method is primarily for setting the field to an undocumented or not + * yet supported value. + */ + fun cdpHeaders(cdpHeaders: JsonField) = apply { + this.cdpHeaders = cdpHeaders + } + fun cdpUrl(cdpUrl: String) = cdpUrl(JsonField.of(cdpUrl)) /** @@ -2120,6 +2156,7 @@ private constructor( LaunchOptions( acceptDownloads, (args ?: JsonMissing.of()).map { it.toImmutable() }, + cdpHeaders, cdpUrl, chromiumSandbox, connectTimeoutMs, @@ -2150,6 +2187,7 @@ private constructor( acceptDownloads() args() + cdpHeaders().ifPresent { it.validate() } cdpUrl() chromiumSandbox() connectTimeoutMs() @@ -2188,6 +2226,7 @@ private constructor( internal fun validity(): Int = (if (acceptDownloads.asKnown().isPresent) 1 else 0) + (args.asKnown().getOrNull()?.size ?: 0) + + (cdpHeaders.asKnown().getOrNull()?.validity() ?: 0) + (if (cdpUrl.asKnown().isPresent) 1 else 0) + (if (chromiumSandbox.asKnown().isPresent) 1 else 0) + (if (connectTimeoutMs.asKnown().isPresent) 1 else 0) + @@ -2206,6 +2245,110 @@ private constructor( (if (userDataDir.asKnown().isPresent) 1 else 0) + (viewport.asKnown().getOrNull()?.validity() ?: 0) + class CdpHeaders + @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 [CdpHeaders]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [CdpHeaders]. */ + class Builder internal constructor() { + + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(cdpHeaders: CdpHeaders) = apply { + additionalProperties = cdpHeaders.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 [CdpHeaders]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): CdpHeaders = CdpHeaders(additionalProperties.toImmutable()) + } + + private var validated: Boolean = false + + fun validate(): CdpHeaders = 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 CdpHeaders && additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = "CdpHeaders{additionalProperties=$additionalProperties}" + } + @JsonDeserialize(using = IgnoreDefaultArgs.Deserializer::class) @JsonSerialize(using = IgnoreDefaultArgs.Serializer::class) class IgnoreDefaultArgs @@ -2872,6 +3015,7 @@ private constructor( return other is LaunchOptions && acceptDownloads == other.acceptDownloads && args == other.args && + cdpHeaders == other.cdpHeaders && cdpUrl == other.cdpUrl && chromiumSandbox == other.chromiumSandbox && connectTimeoutMs == other.connectTimeoutMs && @@ -2896,6 +3040,7 @@ private constructor( Objects.hash( acceptDownloads, args, + cdpHeaders, cdpUrl, chromiumSandbox, connectTimeoutMs, @@ -2920,7 +3065,7 @@ private constructor( override fun hashCode(): Int = hashCode override fun toString() = - "LaunchOptions{acceptDownloads=$acceptDownloads, args=$args, cdpUrl=$cdpUrl, chromiumSandbox=$chromiumSandbox, connectTimeoutMs=$connectTimeoutMs, deviceScaleFactor=$deviceScaleFactor, devtools=$devtools, downloadsPath=$downloadsPath, executablePath=$executablePath, hasTouch=$hasTouch, headless=$headless, ignoreDefaultArgs=$ignoreDefaultArgs, ignoreHttpsErrors=$ignoreHttpsErrors, locale=$locale, port=$port, preserveUserDataDir=$preserveUserDataDir, proxy=$proxy, userDataDir=$userDataDir, viewport=$viewport, additionalProperties=$additionalProperties}" + "LaunchOptions{acceptDownloads=$acceptDownloads, args=$args, cdpHeaders=$cdpHeaders, cdpUrl=$cdpUrl, chromiumSandbox=$chromiumSandbox, connectTimeoutMs=$connectTimeoutMs, deviceScaleFactor=$deviceScaleFactor, devtools=$devtools, downloadsPath=$downloadsPath, executablePath=$executablePath, hasTouch=$hasTouch, headless=$headless, ignoreDefaultArgs=$ignoreDefaultArgs, ignoreHttpsErrors=$ignoreHttpsErrors, locale=$locale, port=$port, preserveUserDataDir=$preserveUserDataDir, proxy=$proxy, userDataDir=$userDataDir, viewport=$viewport, additionalProperties=$additionalProperties}" } /** Browser type to use */ 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 e22e941..5b35aaa 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 @@ -22,6 +22,11 @@ internal class SessionStartParamsTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -168,6 +173,11 @@ internal class SessionStartParamsTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -331,6 +341,11 @@ internal class SessionStartParamsTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -478,6 +493,11 @@ internal class SessionStartParamsTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) 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 a28b993..a123b5c 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 @@ -84,6 +84,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -261,6 +270,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -438,6 +456,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -615,6 +642,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -792,6 +828,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -969,6 +1014,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -1146,6 +1200,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -1323,6 +1386,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -1500,6 +1572,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -1677,6 +1758,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -1854,6 +1944,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2031,6 +2130,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2208,6 +2316,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2385,6 +2502,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2562,6 +2688,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2739,6 +2874,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) @@ -2914,6 +3058,15 @@ internal class ErrorHandlingTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty( + "foo", + JsonValue.from("string"), + ) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) 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 f3a40b7..33f3131 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 @@ -58,6 +58,11 @@ internal class ServiceParamsTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) 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 2a0e601..ce43fba 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 @@ -493,6 +493,12 @@ internal class SessionServiceAsyncTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) 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 dedab31..3392149 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 @@ -486,6 +486,12 @@ internal class SessionServiceTest { SessionStartParams.Browser.LaunchOptions.builder() .acceptDownloads(true) .addArg("string") + .cdpHeaders( + SessionStartParams.Browser.LaunchOptions.CdpHeaders + .builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) .cdpUrl("cdpUrl") .chromiumSandbox(true) .connectTimeoutMs(0.0) From b276ce16e00cbd0a147ebac3747687e26d91ce7c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:33:28 +0000 Subject: [PATCH 5/7] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a959f06..cefb031 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-573d364768ac1902ee5ed8b2485d3b293bda0ea8ff7898aef1a3fd6be79b594a.yml openapi_spec_hash: 107ec840f4330885dd2232a05a66fed7 -config_hash: 2a565ad6662259a2e90fa5f1f5095525 +config_hash: 0209737a4ab2a71afececb0ff7459998 From 76ea3fc583bfe9ca7295856db85378b44d765b0f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:23:01 +0000 Subject: [PATCH 6/7] fix(client): incorrect `Retry-After` parsing --- .../api/core/http/RetryingHttpClient.kt | 2 +- .../api/core/http/RetryingHttpClientTest.kt | 224 +++++++++++++++--- 2 files changed, 192 insertions(+), 34 deletions(-) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt index 5ddfb06..381b36f 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/RetryingHttpClient.kt @@ -201,7 +201,7 @@ private constructor( ?: headers.values("Retry-After").getOrNull(0)?.let { retryAfter -> retryAfter.toFloatOrNull()?.times(TimeUnit.SECONDS.toNanos(1)) ?: try { - ChronoUnit.MILLIS.between( + ChronoUnit.NANOS.between( OffsetDateTime.now(clock), OffsetDateTime.parse( retryAfter, diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/RetryingHttpClientTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/RetryingHttpClientTest.kt index ce14c91..46d5ade 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/RetryingHttpClientTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/RetryingHttpClientTest.kt @@ -20,7 +20,11 @@ import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest import com.github.tomakehurst.wiremock.stubbing.Scenario import java.io.InputStream +import java.time.Clock import java.time.Duration +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter import java.util.concurrent.CompletableFuture import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -36,6 +40,21 @@ internal class RetryingHttpClientTest { private lateinit var baseUrl: String private lateinit var httpClient: HttpClient + private class RecordingSleeper : Sleeper { + val durations = mutableListOf() + + override fun sleep(duration: Duration) { + durations.add(duration) + } + + override fun sleepAsync(duration: Duration): CompletableFuture { + durations.add(duration) + return CompletableFuture.completedFuture(null) + } + + override fun close() {} + } + @BeforeEach fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { baseUrl = wmRuntimeInfo.httpBaseUrl @@ -86,7 +105,8 @@ internal class RetryingHttpClientTest { @ValueSource(booleans = [false, true]) fun execute(async: Boolean) { stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) - val retryingClient = retryingHttpClientBuilder().build() + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).build() val response = retryingClient.execute( @@ -100,6 +120,7 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() assertNoResponseLeaks() } @@ -111,8 +132,12 @@ internal class RetryingHttpClientTest { .withHeader("X-Some-Header", matching("stainless-java-retry-.+")) .willReturn(ok()) ) + val sleeper = RecordingSleeper() val retryingClient = - retryingHttpClientBuilder().maxRetries(2).idempotencyHeader("X-Some-Header").build() + retryingHttpClientBuilder(sleeper) + .maxRetries(2) + .idempotencyHeader("X-Some-Header") + .build() val response = retryingClient.execute( @@ -126,20 +151,20 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() assertNoResponseLeaks() } @ParameterizedTest @ValueSource(booleans = [false, true]) fun execute_withRetryAfterHeader(async: Boolean) { + val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT" stubFor( post(urlPathEqualTo("/something")) // First we fail with a retry after header given as a date .inScenario("foo") .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT") - ) + .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate)) .willSetStateTo("RETRY_AFTER_DATE") ) stubFor( @@ -158,7 +183,13 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(2).build() + // Fix the clock to 5 seconds before the Retry-After date so the date-based backoff is + // deterministic. + val retryAfterDateTime = + OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME) + val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build() val response = retryingClient.execute( @@ -186,19 +217,20 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("2")), ) + assertThat(sleeper.durations) + .containsExactly(Duration.ofSeconds(5), Duration.ofMillis(1234)) assertNoResponseLeaks() } @ParameterizedTest @ValueSource(booleans = [false, true]) fun execute_withOverwrittenRetryCountHeader(async: Boolean) { + val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT" stubFor( post(urlPathEqualTo("/something")) .inScenario("foo") // first we fail with a retry after header given as a date .whenScenarioStateIs(Scenario.STARTED) - .willReturn( - serviceUnavailable().withHeader("Retry-After", "Wed, 21 Oct 2015 07:28:00 GMT") - ) + .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate)) .willSetStateTo("RETRY_AFTER_DATE") ) stubFor( @@ -208,7 +240,11 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(2).build() + val retryAfterDateTime = + OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME) + val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build() val response = retryingClient.execute( @@ -227,6 +263,7 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("42")), ) + assertThat(sleeper.durations).containsExactly(Duration.ofSeconds(5)) assertNoResponseLeaks() } @@ -247,7 +284,8 @@ internal class RetryingHttpClientTest { .willReturn(ok()) .willSetStateTo("COMPLETED") ) - val retryingClient = retryingHttpClientBuilder().maxRetries(1).build() + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() val response = retryingClient.execute( @@ -261,6 +299,7 @@ internal class RetryingHttpClientTest { assertThat(response.statusCode()).isEqualTo(200) verify(2, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(10)) assertNoResponseLeaks() } @@ -301,21 +340,12 @@ internal class RetryingHttpClientTest { override fun close() = httpClient.close() } + val sleeper = RecordingSleeper() val retryingClient = RetryingHttpClient.builder() .httpClient(failingHttpClient) .maxRetries(2) - .sleeper( - object : Sleeper { - - override fun sleep(duration: Duration) {} - - override fun sleepAsync(duration: Duration): CompletableFuture = - CompletableFuture.completedFuture(null) - - override fun close() {} - } - ) + .sleeper(sleeper) .build() val response = @@ -339,25 +369,153 @@ internal class RetryingHttpClientTest { postRequestedFor(urlPathEqualTo("/something")) .withHeader("x-stainless-retry-count", equalTo("0")), ) + // Exponential backoff with jitter: 0.5s * jitter where jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) assertNoResponseLeaks() } - private fun retryingHttpClientBuilder() = - RetryingHttpClient.builder() - .httpClient(httpClient) - // Use a no-op `Sleeper` to make the test fast. - .sleeper( - object : Sleeper { + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoff(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(3).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) - override fun sleep(duration: Duration) {} + // All retries exhausted; the last 503 response is returned. + assertThat(response.statusCode()).isEqualTo(503) + verify(4, postRequestedFor(urlPathEqualTo("/something"))) + // Exponential backoff with jitter: backoff = min(0.5 * 2^(retries-1), 8) * jitter where + // jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(3) + // retries=1: 0.5s * [0.75, 1.0] + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + // retries=2: 1.0s * [0.75, 1.0] + assertThat(sleeper.durations[1]).isBetween(Duration.ofMillis(750), Duration.ofMillis(1000)) + // retries=3: 2.0s * [0.75, 1.0] + assertThat(sleeper.durations[2]).isBetween(Duration.ofMillis(1500), Duration.ofMillis(2000)) + assertNoResponseLeaks() + } - override fun sleepAsync(duration: Duration): CompletableFuture = - CompletableFuture.completedFuture(null) + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoffCap(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(6).build() - override fun close() {} - } + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, ) + assertThat(response.statusCode()).isEqualTo(503) + verify(7, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).hasSize(6) + // retries=5: min(0.5 * 2^4, 8) = 8.0s * [0.75, 1.0] + assertThat(sleeper.durations[4]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + // retries=6: min(0.5 * 2^5, 8) = min(16, 8) = 8.0s * [0.75, 1.0] (capped) + assertThat(sleeper.durations[5]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterMsPriorityOverRetryAfter(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + serviceUnavailable() + .withHeader("Retry-After-Ms", "50") + .withHeader("Retry-After", "2") + ) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + // Retry-After-Ms (50ms) takes priority over Retry-After (2s). + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(50)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterUnparseable(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(serviceUnavailable().withHeader("Retry-After", "not-a-date-or-number")) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + // Unparseable Retry-After falls through to exponential backoff. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + assertNoResponseLeaks() + } + + private fun retryingHttpClientBuilder( + sleeper: RecordingSleeper, + clock: Clock = Clock.systemUTC(), + ) = RetryingHttpClient.builder().httpClient(httpClient).sleeper(sleeper).clock(clock) + private fun HttpClient.execute(request: HttpRequest, async: Boolean): HttpResponse = if (async) executeAsync(request).get() else execute(request) From 5c24fd583ef060c04fbf39bd7cf933ea911bf85d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:23:18 +0000 Subject: [PATCH 7/7] release: 0.8.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ README.md | 8 ++++---- build.gradle.kts | 2 +- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1bc5713..6538ca9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.7.1" + ".": "0.8.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 168eb3d..30d9be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.8.0 (2026-03-11) + +Full Changelog: [v0.7.1...v0.8.0](https://github.com/browserbase/stagehand-java/compare/v0.7.1...v0.8.0) + +### Features + +* Add missing cdpHeaders field to v3 server openapi spec ([5a9506d](https://github.com/browserbase/stagehand-java/commit/5a9506d5dfeb1ae172834941aad3bbd9e96f2973)) + + +### Bug Fixes + +* **client:** incorrect `Retry-After` parsing ([76ea3fc](https://github.com/browserbase/stagehand-java/commit/76ea3fc583bfe9ca7295856db85378b44d765b0f)) + + +### Chores + +* **ci:** skip uploading artifacts on stainless-internal branches ([4a1399c](https://github.com/browserbase/stagehand-java/commit/4a1399c7a535d9bf8934e9d142878e57a1537b8d)) +* **internal:** bump palantir-java-format ([3bf036a](https://github.com/browserbase/stagehand-java/commit/3bf036aed5ba8fb90fe4bf5bc6b62a49f09f7dcc)) +* **internal:** codegen related update ([f94c8c9](https://github.com/browserbase/stagehand-java/commit/f94c8c9a86af4592fba5c42645ccc1099a8ab1fe)) + ## 0.7.1 (2026-03-04) Full Changelog: [v0.7.0...v0.7.1](https://github.com/browserbase/stagehand-java/compare/v0.7.0...v0.7.1) diff --git a/README.md b/README.md index 726fa64..a60f49c 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/0.7.1) -[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/0.7.1/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.7.1) +[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-java)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/0.8.0) +[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/0.8.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.8.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:0.7.1") +implementation("com.browserbase.api:stagehand-java:0.8.0") ``` ### Maven @@ -94,7 +94,7 @@ implementation("com.browserbase.api:stagehand-java:0.7.1") com.browserbase.api stagehand-java - 0.7.1 + 0.8.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 79fb854..129fa8a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ repositories { allprojects { group = "com.browserbase.api" - version = "0.7.1" // x-release-please-version + version = "0.8.0" // x-release-please-version } subprojects {