From 058cf3853b589356fe0b88d5899c98e4c44c8f19 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:46:00 +0000 Subject: [PATCH 1/6] feat: /end endpoint returns empty object --- .stats.yml | 6 +- .../api/models/sessions/SessionEndParams.kt | 66 ++++++++----------- .../api/services/async/SessionServiceAsync.kt | 44 ++++--------- .../services/async/SessionServiceAsyncImpl.kt | 2 +- .../api/services/blocking/SessionService.kt | 36 +++------- .../services/blocking/SessionServiceImpl.kt | 2 +- .../models/sessions/SessionEndParamsTest.kt | 45 ++++++++++++- .../services/async/SessionServiceAsyncTest.kt | 1 + .../services/blocking/SessionServiceTest.kt | 1 + 9 files changed, 99 insertions(+), 104 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3a11178..1915416 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-ed52466945f2f8dfd3814a29e948d7bf30af7b76a7a7689079c03b8baf64e26f.yml -openapi_spec_hash: 5d57aaf2362b0d882372dbf76477ba23 -config_hash: 8ec9eaf59304f664cf79f73de1488671 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-2c6c017cc9ca1fcfe7b3902edfa64fb0420bdb46b1740c7c862e81e2132d4f7c.yml +openapi_spec_hash: 220daf7e8f5897909a6c10e3385386e3 +config_hash: 1f709f8775e13029dc60064ef3a94355 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 90a656f..6c1186f 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 @@ -6,9 +6,9 @@ import com.browserbase.api.core.Enum import com.browserbase.api.core.JsonField import com.browserbase.api.core.JsonValue import com.browserbase.api.core.Params +import com.browserbase.api.core.checkRequired 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.JsonCreator import java.time.OffsetDateTime @@ -25,9 +25,9 @@ private constructor( private val xSdkVersion: String?, private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, + private val body: JsonValue, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, - private val additionalBodyProperties: Map, ) : Params { /** Unique session identifier */ @@ -45,8 +45,7 @@ private constructor( /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) - /** Additional body properties to send with the request. */ - fun _additionalBodyProperties(): Map = additionalBodyProperties + fun body(): JsonValue = body /** Additional headers to send with the request. */ fun _additionalHeaders(): Headers = additionalHeaders @@ -58,9 +57,14 @@ private constructor( companion object { - @JvmStatic fun none(): SessionEndParams = builder().build() - - /** Returns a mutable builder for constructing an instance of [SessionEndParams]. */ + /** + * Returns a mutable builder for constructing an instance of [SessionEndParams]. + * + * The following fields are required: + * ```java + * .body() + * ``` + */ @JvmStatic fun builder() = Builder() } @@ -72,9 +76,9 @@ private constructor( private var xSdkVersion: String? = null private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null + private var body: JsonValue? = null private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() - private var additionalBodyProperties: MutableMap = mutableMapOf() @JvmSynthetic internal fun from(sessionEndParams: SessionEndParams) = apply { @@ -83,9 +87,9 @@ private constructor( xSdkVersion = sessionEndParams.xSdkVersion xSentAt = sessionEndParams.xSentAt xStreamResponse = sessionEndParams.xStreamResponse + body = sessionEndParams.body additionalHeaders = sessionEndParams.additionalHeaders.toBuilder() additionalQueryParams = sessionEndParams.additionalQueryParams.toBuilder() - additionalBodyProperties = sessionEndParams.additionalBodyProperties.toMutableMap() } /** Unique session identifier */ @@ -121,6 +125,8 @@ private constructor( fun xStreamResponse(xStreamResponse: Optional) = xStreamResponse(xStreamResponse.getOrNull()) + fun body(body: JsonValue) = apply { this.body = body } + fun additionalHeaders(additionalHeaders: Headers) = apply { this.additionalHeaders.clear() putAllAdditionalHeaders(additionalHeaders) @@ -219,32 +225,17 @@ private constructor( additionalQueryParams.removeAll(keys) } - fun additionalBodyProperties(additionalBodyProperties: Map) = apply { - this.additionalBodyProperties.clear() - putAllAdditionalBodyProperties(additionalBodyProperties) - } - - fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { - additionalBodyProperties.put(key, value) - } - - fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = - apply { - this.additionalBodyProperties.putAll(additionalBodyProperties) - } - - fun removeAdditionalBodyProperty(key: String) = apply { - additionalBodyProperties.remove(key) - } - - fun removeAllAdditionalBodyProperties(keys: Set) = apply { - keys.forEach(::removeAdditionalBodyProperty) - } - /** * Returns an immutable instance of [SessionEndParams]. * * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .body() + * ``` + * + * @throws IllegalStateException if any required field is unset. */ fun build(): SessionEndParams = SessionEndParams( @@ -253,14 +244,13 @@ private constructor( xSdkVersion, xSentAt, xStreamResponse, + checkRequired("body", body), additionalHeaders.build(), additionalQueryParams.build(), - additionalBodyProperties.toImmutable(), ) } - fun _body(): Optional> = - Optional.ofNullable(additionalBodyProperties.ifEmpty { null }) + fun _body(): JsonValue = body fun _pathParam(index: Int): String = when (index) { @@ -560,9 +550,9 @@ private constructor( xSdkVersion == other.xSdkVersion && xSentAt == other.xSentAt && xStreamResponse == other.xStreamResponse && + body == other.body && additionalHeaders == other.additionalHeaders && - additionalQueryParams == other.additionalQueryParams && - additionalBodyProperties == other.additionalBodyProperties + additionalQueryParams == other.additionalQueryParams } override fun hashCode(): Int = @@ -572,11 +562,11 @@ private constructor( xSdkVersion, xSentAt, xStreamResponse, + body, additionalHeaders, additionalQueryParams, - additionalBodyProperties, ) override fun toString() = - "SessionEndParams{id=$id, xLanguage=$xLanguage, xSdkVersion=$xSdkVersion, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams, additionalBodyProperties=$additionalBodyProperties}" + "SessionEndParams{id=$id, xLanguage=$xLanguage, xSdkVersion=$xSdkVersion, xSentAt=$xSentAt, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}" } diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt index 5c9171e..f2dbbb3 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt @@ -89,21 +89,20 @@ interface SessionServiceAsync { ): AsyncStreamResponse /** Terminates the browser session and releases all associated resources. */ - fun end(id: String): CompletableFuture = end(id, SessionEndParams.none()) + fun end(id: String, params: SessionEndParams): CompletableFuture = + end(id, params, RequestOptions.none()) /** @see end */ fun end( id: String, - params: SessionEndParams = SessionEndParams.none(), + params: SessionEndParams, requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ - fun end( - id: String, - params: SessionEndParams = SessionEndParams.none(), - ): CompletableFuture = end(id, params, RequestOptions.none()) + fun end(params: SessionEndParams): CompletableFuture = + end(params, RequestOptions.none()) /** @see end */ fun end( @@ -111,14 +110,6 @@ interface SessionServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture - /** @see end */ - fun end(params: SessionEndParams): CompletableFuture = - end(params, RequestOptions.none()) - - /** @see end */ - fun end(id: String, requestOptions: RequestOptions): CompletableFuture = - end(id, SessionEndParams.none(), requestOptions) - /** Runs an autonomous AI agent that can perform complex multi-step browser tasks. */ fun execute( id: String, @@ -430,29 +421,19 @@ interface SessionServiceAsync { * Returns a raw HTTP response for `post /v1/sessions/{id}/end`, but is otherwise the same * as [SessionServiceAsync.end]. */ - fun end(id: String): CompletableFuture> = - end(id, SessionEndParams.none()) - - /** @see end */ fun end( id: String, - params: SessionEndParams = SessionEndParams.none(), - requestOptions: RequestOptions = RequestOptions.none(), - ): CompletableFuture> = - end(params.toBuilder().id(id).build(), requestOptions) - - /** @see end */ - fun end( - id: String, - params: SessionEndParams = SessionEndParams.none(), + params: SessionEndParams, ): CompletableFuture> = end(id, params, RequestOptions.none()) /** @see end */ fun end( + id: String, params: SessionEndParams, requestOptions: RequestOptions = RequestOptions.none(), - ): CompletableFuture> + ): CompletableFuture> = + end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ fun end(params: SessionEndParams): CompletableFuture> = @@ -460,10 +441,9 @@ interface SessionServiceAsync { /** @see end */ fun end( - id: String, - requestOptions: RequestOptions, - ): CompletableFuture> = - end(id, SessionEndParams.none(), requestOptions) + params: SessionEndParams, + requestOptions: RequestOptions = RequestOptions.none(), + ): CompletableFuture> /** * Returns a raw HTTP response for `post /v1/sessions/{id}/agentExecute`, but is otherwise diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsyncImpl.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsyncImpl.kt index 82062e4..6b354a0 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsyncImpl.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsyncImpl.kt @@ -250,7 +250,7 @@ class SessionServiceAsyncImpl internal constructor(private val clientOptions: Cl .method(HttpMethod.POST) .baseUrl(clientOptions.baseUrl()) .addPathSegments("v1", "sessions", params._pathParam(0), "end") - .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .body(json(clientOptions.jsonMapper, params._body())) .build() .prepareAsync(clientOptions, params) val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt index 0d56e93..2ee1202 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt @@ -88,18 +88,18 @@ interface SessionService { ): StreamResponse /** Terminates the browser session and releases all associated resources. */ - fun end(id: String): SessionEndResponse = end(id, SessionEndParams.none()) + fun end(id: String, params: SessionEndParams): SessionEndResponse = + end(id, params, RequestOptions.none()) /** @see end */ fun end( id: String, - params: SessionEndParams = SessionEndParams.none(), + params: SessionEndParams, requestOptions: RequestOptions = RequestOptions.none(), ): SessionEndResponse = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ - fun end(id: String, params: SessionEndParams = SessionEndParams.none()): SessionEndResponse = - end(id, params, RequestOptions.none()) + fun end(params: SessionEndParams): SessionEndResponse = end(params, RequestOptions.none()) /** @see end */ fun end( @@ -107,13 +107,6 @@ interface SessionService { requestOptions: RequestOptions = RequestOptions.none(), ): SessionEndResponse - /** @see end */ - fun end(params: SessionEndParams): SessionEndResponse = end(params, RequestOptions.none()) - - /** @see end */ - fun end(id: String, requestOptions: RequestOptions): SessionEndResponse = - end(id, SessionEndParams.none(), requestOptions) - /** Runs an autonomous AI agent that can perform complex multi-step browser tasks. */ fun execute(id: String, params: SessionExecuteParams): SessionExecuteResponse = execute(id, params, RequestOptions.none()) @@ -413,23 +406,22 @@ interface SessionService { * as [SessionService.end]. */ @MustBeClosed - fun end(id: String): HttpResponseFor = end(id, SessionEndParams.none()) + fun end(id: String, params: SessionEndParams): HttpResponseFor = + end(id, params, RequestOptions.none()) /** @see end */ @MustBeClosed fun end( id: String, - params: SessionEndParams = SessionEndParams.none(), + params: SessionEndParams, requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ @MustBeClosed - fun end( - id: String, - params: SessionEndParams = SessionEndParams.none(), - ): HttpResponseFor = end(id, params, RequestOptions.none()) + fun end(params: SessionEndParams): HttpResponseFor = + end(params, RequestOptions.none()) /** @see end */ @MustBeClosed @@ -438,16 +430,6 @@ interface SessionService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor - /** @see end */ - @MustBeClosed - fun end(params: SessionEndParams): HttpResponseFor = - end(params, RequestOptions.none()) - - /** @see end */ - @MustBeClosed - fun end(id: String, requestOptions: RequestOptions): HttpResponseFor = - end(id, SessionEndParams.none(), requestOptions) - /** * Returns a raw HTTP response for `post /v1/sessions/{id}/agentExecute`, but is otherwise * the same as [SessionService.execute]. diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionServiceImpl.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionServiceImpl.kt index 4bfce1b..f064761 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionServiceImpl.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionServiceImpl.kt @@ -223,7 +223,7 @@ class SessionServiceImpl internal constructor(private val clientOptions: ClientO .method(HttpMethod.POST) .baseUrl(clientOptions.baseUrl()) .addPathSegments("v1", "sessions", params._pathParam(0), "end") - .apply { params._body().ifPresent { body(json(clientOptions.jsonMapper, it)) } } + .body(json(clientOptions.jsonMapper, params._body())) .build() .prepare(clientOptions, params) val requestOptions = requestOptions.applyDefaults(RequestOptions.from(clientOptions)) diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt index bc50272..120519f 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt @@ -2,6 +2,7 @@ package com.browserbase.api.models.sessions +import com.browserbase.api.core.JsonValue import com.browserbase.api.core.http.Headers import java.time.OffsetDateTime import org.assertj.core.api.Assertions.assertThat @@ -17,12 +18,17 @@ internal class SessionEndParamsTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) + .body(JsonValue.from(mapOf())) .build() } @Test fun pathParams() { - val params = SessionEndParams.builder().id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123").build() + val params = + SessionEndParams.builder() + .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") + .body(JsonValue.from(mapOf())) + .build() assertThat(params._pathParam(0)).isEqualTo("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") // out-of-bound path param @@ -38,6 +44,7 @@ internal class SessionEndParamsTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) + .body(JsonValue.from(mapOf())) .build() val headers = params._headers() @@ -55,10 +62,44 @@ internal class SessionEndParamsTest { @Test fun headersWithoutOptionalFields() { - val params = SessionEndParams.builder().id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123").build() + val params = + SessionEndParams.builder() + .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") + .body(JsonValue.from(mapOf())) + .build() val headers = params._headers() assertThat(headers).isEqualTo(Headers.builder().build()) } + + @Test + fun body() { + val params = + SessionEndParams.builder() + .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") + .xLanguage(SessionEndParams.XLanguage.TYPESCRIPT) + .xSdkVersion("3.0.6") + .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) + .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) + .body(JsonValue.from(mapOf())) + .build() + + val body = params._body() + + assertThat(body).isEqualTo(JsonValue.from(mapOf())) + } + + @Test + fun bodyWithoutOptionalFields() { + val params = + SessionEndParams.builder() + .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") + .body(JsonValue.from(mapOf())) + .build() + + val body = params._body() + + assertThat(body).isEqualTo(JsonValue.from(mapOf())) + } } 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 f9bc4be..59dd056 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 @@ -121,6 +121,7 @@ internal class SessionServiceAsyncTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) + .body(JsonValue.from(mapOf())) .build() ) 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 1bad240..ceec71b 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 @@ -120,6 +120,7 @@ internal class SessionServiceTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) + .body(JsonValue.from(mapOf())) .build() ) From 5a4d99fd20473dcd6045fb65d8230c3894734364 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 04:53:22 +0000 Subject: [PATCH 2/6] feat(client): add `HttpRequest#url()` method --- .../browserbase/api/core/http/HttpRequest.kt | 30 +++++ .../api/core/http/HttpRequestTest.kt | 110 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/HttpRequestTest.kt diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/HttpRequest.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/HttpRequest.kt index f42f44a..280bd51 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/HttpRequest.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/HttpRequest.kt @@ -2,6 +2,7 @@ package com.browserbase.api.core.http import com.browserbase.api.core.checkRequired import com.browserbase.api.core.toImmutable +import java.net.URLEncoder class HttpRequest private constructor( @@ -13,6 +14,35 @@ private constructor( @get:JvmName("body") val body: HttpRequestBody?, ) { + fun url(): String = buildString { + append(baseUrl) + + pathSegments.forEach { segment -> + if (!endsWith("/")) { + append("/") + } + append(URLEncoder.encode(segment, "UTF-8")) + } + + if (queryParams.isEmpty()) { + return@buildString + } + + append("?") + var isFirst = true + queryParams.keys().forEach { key -> + queryParams.values(key).forEach { value -> + if (!isFirst) { + append("&") + } + append(URLEncoder.encode(key, "UTF-8")) + append("=") + append(URLEncoder.encode(value, "UTF-8")) + isFirst = false + } + } + } + fun toBuilder(): Builder = Builder().from(this) override fun toString(): String = diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/HttpRequestTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/HttpRequestTest.kt new file mode 100644 index 0000000..5785890 --- /dev/null +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/core/http/HttpRequestTest.kt @@ -0,0 +1,110 @@ +package com.browserbase.api.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class HttpRequestTest { + + enum class UrlTestCase(val request: HttpRequest, val expectedUrl: String) { + BASE_URL_ONLY( + HttpRequest.builder().method(HttpMethod.GET).baseUrl("https://api.example.com").build(), + expectedUrl = "https://api.example.com", + ), + BASE_URL_WITH_TRAILING_SLASH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .build(), + expectedUrl = "https://api.example.com/", + ), + SINGLE_PATH_SEGMENT( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + MULTIPLE_PATH_SEGMENTS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegments("users", "123", "profile") + .build(), + expectedUrl = "https://api.example.com/users/123/profile", + ), + PATH_SEGMENT_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("user name") + .build(), + expectedUrl = "https://api.example.com/user+name", + ), + SINGLE_QUERY_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .build(), + expectedUrl = "https://api.example.com/users?limit=10", + ), + MULTIPLE_QUERY_PARAMS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .putQueryParam("offset", "20") + .build(), + expectedUrl = "https://api.example.com/users?limit=10&offset=20", + ), + QUERY_PARAM_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("search") + .putQueryParam("q", "hello world") + .build(), + expectedUrl = "https://api.example.com/search?q=hello+world", + ), + MULTIPLE_VALUES_SAME_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParams("tags", listOf("admin", "user")) + .build(), + expectedUrl = "https://api.example.com/users?tags=admin&tags=user", + ), + BASE_URL_WITH_TRAILING_SLASH_AND_PATH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + COMPLEX_URL( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl("https://api.example.com") + .addPathSegments("v1", "users", "123") + .putQueryParams("include", listOf("profile", "settings")) + .putQueryParam("format", "json") + .build(), + expectedUrl = + "https://api.example.com/v1/users/123?include=profile&include=settings&format=json", + ), + } + + @ParameterizedTest + @EnumSource + fun url(testCase: UrlTestCase) { + val actualUrl = testCase.request.url() + + assertThat(actualUrl).isEqualTo(testCase.expectedUrl) + } +} From 0c7cb0241f65547071fcd15e7bce41f621cfbfcb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:02:42 +0000 Subject: [PATCH 3/6] docs: prominently feature MCP server setup in root SDK readmes --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 35c6e6a..4e80314 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ The Stagehand Java SDK provides convenient access to the [Stagehand REST API](ht It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Stagehand MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=stagehand-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInN0YWdlaGFuZC1tY3AiXX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22stagehand-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22stagehand-mcp%22%5D%7D) + +> Note: You may need to set environment variables in your MCP client. + The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.4.0). From a93f1bf0324f4fa0b1bd96122d29534ff03cd9b2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:34:03 +0000 Subject: [PATCH 4/6] feat: Added optional param to force empty object --- .stats.yml | 4 +- .../api/models/sessions/SessionEndParams.kt | 186 +++++++++++++++--- .../api/services/async/SessionServiceAsync.kt | 44 +++-- .../api/services/blocking/SessionService.kt | 36 +++- .../models/sessions/SessionEndParamsTest.kt | 28 +-- .../services/async/SessionServiceAsyncTest.kt | 2 +- .../services/blocking/SessionServiceTest.kt | 2 +- 7 files changed, 233 insertions(+), 69 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1915416..f4f95e8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-2c6c017cc9ca1fcfe7b3902edfa64fb0420bdb46b1740c7c862e81e2132d4f7c.yml -openapi_spec_hash: 220daf7e8f5897909a6c10e3385386e3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-39cd9547d16412cf0568f6ce2ad8d43805dffe65bde830beeff630b903ae3b38.yml +openapi_spec_hash: 9cd7c9fefa686f9711392782d948470f config_hash: 1f709f8775e13029dc60064ef3a94355 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 6c1186f..e10e438 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 @@ -3,16 +3,21 @@ package com.browserbase.api.models.sessions import com.browserbase.api.core.Enum +import com.browserbase.api.core.ExcludeMissing import com.browserbase.api.core.JsonField +import com.browserbase.api.core.JsonMissing import com.browserbase.api.core.JsonValue import com.browserbase.api.core.Params -import com.browserbase.api.core.checkRequired import com.browserbase.api.core.http.Headers import com.browserbase.api.core.http.QueryParams import com.browserbase.api.errors.StagehandInvalidDataException +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import java.util.Collections import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull @@ -25,7 +30,7 @@ private constructor( private val xSdkVersion: String?, private val xSentAt: OffsetDateTime?, private val xStreamResponse: XStreamResponse?, - private val body: JsonValue, + private val body: Body, private val additionalHeaders: Headers, private val additionalQueryParams: QueryParams, ) : Params { @@ -45,7 +50,9 @@ private constructor( /** Whether to stream the response via SSE */ fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse) - fun body(): JsonValue = body + fun __forceBody(): JsonValue = body.__forceBody() + + fun _additionalBodyProperties(): Map = body._additionalProperties() /** Additional headers to send with the request. */ fun _additionalHeaders(): Headers = additionalHeaders @@ -57,14 +64,9 @@ private constructor( companion object { - /** - * Returns a mutable builder for constructing an instance of [SessionEndParams]. - * - * The following fields are required: - * ```java - * .body() - * ``` - */ + @JvmStatic fun none(): SessionEndParams = builder().build() + + /** Returns a mutable builder for constructing an instance of [SessionEndParams]. */ @JvmStatic fun builder() = Builder() } @@ -76,7 +78,7 @@ private constructor( private var xSdkVersion: String? = null private var xSentAt: OffsetDateTime? = null private var xStreamResponse: XStreamResponse? = null - private var body: JsonValue? = null + private var body: Body.Builder = Body.builder() private var additionalHeaders: Headers.Builder = Headers.builder() private var additionalQueryParams: QueryParams.Builder = QueryParams.builder() @@ -87,7 +89,7 @@ private constructor( xSdkVersion = sessionEndParams.xSdkVersion xSentAt = sessionEndParams.xSentAt xStreamResponse = sessionEndParams.xStreamResponse - body = sessionEndParams.body + body = sessionEndParams.body.toBuilder() additionalHeaders = sessionEndParams.additionalHeaders.toBuilder() additionalQueryParams = sessionEndParams.additionalQueryParams.toBuilder() } @@ -125,7 +127,35 @@ private constructor( fun xStreamResponse(xStreamResponse: Optional) = xStreamResponse(xStreamResponse.getOrNull()) - fun body(body: JsonValue) = apply { this.body = body } + /** + * Sets the entire request body. + * + * This is generally only useful if you are already constructing the body separately. + * Otherwise, it's more convenient to use the top-level setters instead: + * - [_forceBody] + */ + fun body(body: Body) = apply { this.body = body.toBuilder() } + + fun _forceBody(_forceBody: JsonValue) = apply { body._forceBody(_forceBody) } + + fun additionalBodyProperties(additionalBodyProperties: Map) = apply { + body.additionalProperties(additionalBodyProperties) + } + + fun putAdditionalBodyProperty(key: String, value: JsonValue) = apply { + body.putAdditionalProperty(key, value) + } + + fun putAllAdditionalBodyProperties(additionalBodyProperties: Map) = + apply { + body.putAllAdditionalProperties(additionalBodyProperties) + } + + fun removeAdditionalBodyProperty(key: String) = apply { body.removeAdditionalProperty(key) } + + fun removeAllAdditionalBodyProperties(keys: Set) = apply { + body.removeAllAdditionalProperties(keys) + } fun additionalHeaders(additionalHeaders: Headers) = apply { this.additionalHeaders.clear() @@ -229,13 +259,6 @@ private constructor( * Returns an immutable instance of [SessionEndParams]. * * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .body() - * ``` - * - * @throws IllegalStateException if any required field is unset. */ fun build(): SessionEndParams = SessionEndParams( @@ -244,13 +267,13 @@ private constructor( xSdkVersion, xSentAt, xStreamResponse, - checkRequired("body", body), + body.build(), additionalHeaders.build(), additionalQueryParams.build(), ) } - fun _body(): JsonValue = body + fun _body(): Body = body fun _pathParam(index: Int): String = when (index) { @@ -271,6 +294,123 @@ private constructor( override fun _queryParams(): QueryParams = additionalQueryParams + class Body + @JsonCreator(mode = JsonCreator.Mode.DISABLED) + private constructor( + private val _forceBody: JsonValue, + private val additionalProperties: MutableMap, + ) { + + @JsonCreator + private constructor( + @JsonProperty("_forceBody") @ExcludeMissing _forceBody: JsonValue = JsonMissing.of() + ) : this(_forceBody, mutableMapOf()) + + @JsonProperty("_forceBody") @ExcludeMissing fun __forceBody(): JsonValue = _forceBody + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** Returns a mutable builder for constructing an instance of [Body]. */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [Body]. */ + class Builder internal constructor() { + + private var _forceBody: JsonValue = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(body: Body) = apply { + _forceBody = body._forceBody + additionalProperties = body.additionalProperties.toMutableMap() + } + + fun _forceBody(_forceBody: JsonValue) = apply { this._forceBody = _forceBody } + + 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 [Body]. + * + * Further updates to this [Builder] will not mutate the returned instance. + */ + fun build(): Body = Body(_forceBody, additionalProperties.toMutableMap()) + } + + private var validated: Boolean = false + + fun validate(): Body = 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 = 0 + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is Body && + _forceBody == other._forceBody && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(_forceBody, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "Body{_forceBody=$_forceBody, additionalProperties=$additionalProperties}" + } + /** Client SDK language */ class XLanguage @JsonCreator private constructor(private val value: JsonField) : Enum { diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt index f2dbbb3..5c9171e 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/async/SessionServiceAsync.kt @@ -89,20 +89,21 @@ interface SessionServiceAsync { ): AsyncStreamResponse /** Terminates the browser session and releases all associated resources. */ - fun end(id: String, params: SessionEndParams): CompletableFuture = - end(id, params, RequestOptions.none()) + fun end(id: String): CompletableFuture = end(id, SessionEndParams.none()) /** @see end */ fun end( id: String, - params: SessionEndParams, + params: SessionEndParams = SessionEndParams.none(), requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ - fun end(params: SessionEndParams): CompletableFuture = - end(params, RequestOptions.none()) + fun end( + id: String, + params: SessionEndParams = SessionEndParams.none(), + ): CompletableFuture = end(id, params, RequestOptions.none()) /** @see end */ fun end( @@ -110,6 +111,14 @@ interface SessionServiceAsync { requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture + /** @see end */ + fun end(params: SessionEndParams): CompletableFuture = + end(params, RequestOptions.none()) + + /** @see end */ + fun end(id: String, requestOptions: RequestOptions): CompletableFuture = + end(id, SessionEndParams.none(), requestOptions) + /** Runs an autonomous AI agent that can perform complex multi-step browser tasks. */ fun execute( id: String, @@ -421,19 +430,29 @@ interface SessionServiceAsync { * Returns a raw HTTP response for `post /v1/sessions/{id}/end`, but is otherwise the same * as [SessionServiceAsync.end]. */ + fun end(id: String): CompletableFuture> = + end(id, SessionEndParams.none()) + + /** @see end */ fun end( id: String, - params: SessionEndParams, + params: SessionEndParams = SessionEndParams.none(), + requestOptions: RequestOptions = RequestOptions.none(), ): CompletableFuture> = - end(id, params, RequestOptions.none()) + end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ fun end( id: String, + params: SessionEndParams = SessionEndParams.none(), + ): CompletableFuture> = + end(id, params, RequestOptions.none()) + + /** @see end */ + fun end( params: SessionEndParams, requestOptions: RequestOptions = RequestOptions.none(), - ): CompletableFuture> = - end(params.toBuilder().id(id).build(), requestOptions) + ): CompletableFuture> /** @see end */ fun end(params: SessionEndParams): CompletableFuture> = @@ -441,9 +460,10 @@ interface SessionServiceAsync { /** @see end */ fun end( - params: SessionEndParams, - requestOptions: RequestOptions = RequestOptions.none(), - ): CompletableFuture> + id: String, + requestOptions: RequestOptions, + ): CompletableFuture> = + end(id, SessionEndParams.none(), requestOptions) /** * Returns a raw HTTP response for `post /v1/sessions/{id}/agentExecute`, but is otherwise diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt index 2ee1202..0d56e93 100644 --- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt +++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/services/blocking/SessionService.kt @@ -88,18 +88,18 @@ interface SessionService { ): StreamResponse /** Terminates the browser session and releases all associated resources. */ - fun end(id: String, params: SessionEndParams): SessionEndResponse = - end(id, params, RequestOptions.none()) + fun end(id: String): SessionEndResponse = end(id, SessionEndParams.none()) /** @see end */ fun end( id: String, - params: SessionEndParams, + params: SessionEndParams = SessionEndParams.none(), requestOptions: RequestOptions = RequestOptions.none(), ): SessionEndResponse = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ - fun end(params: SessionEndParams): SessionEndResponse = end(params, RequestOptions.none()) + fun end(id: String, params: SessionEndParams = SessionEndParams.none()): SessionEndResponse = + end(id, params, RequestOptions.none()) /** @see end */ fun end( @@ -107,6 +107,13 @@ interface SessionService { requestOptions: RequestOptions = RequestOptions.none(), ): SessionEndResponse + /** @see end */ + fun end(params: SessionEndParams): SessionEndResponse = end(params, RequestOptions.none()) + + /** @see end */ + fun end(id: String, requestOptions: RequestOptions): SessionEndResponse = + end(id, SessionEndParams.none(), requestOptions) + /** Runs an autonomous AI agent that can perform complex multi-step browser tasks. */ fun execute(id: String, params: SessionExecuteParams): SessionExecuteResponse = execute(id, params, RequestOptions.none()) @@ -406,22 +413,23 @@ interface SessionService { * as [SessionService.end]. */ @MustBeClosed - fun end(id: String, params: SessionEndParams): HttpResponseFor = - end(id, params, RequestOptions.none()) + fun end(id: String): HttpResponseFor = end(id, SessionEndParams.none()) /** @see end */ @MustBeClosed fun end( id: String, - params: SessionEndParams, + params: SessionEndParams = SessionEndParams.none(), requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor = end(params.toBuilder().id(id).build(), requestOptions) /** @see end */ @MustBeClosed - fun end(params: SessionEndParams): HttpResponseFor = - end(params, RequestOptions.none()) + fun end( + id: String, + params: SessionEndParams = SessionEndParams.none(), + ): HttpResponseFor = end(id, params, RequestOptions.none()) /** @see end */ @MustBeClosed @@ -430,6 +438,16 @@ interface SessionService { requestOptions: RequestOptions = RequestOptions.none(), ): HttpResponseFor + /** @see end */ + @MustBeClosed + fun end(params: SessionEndParams): HttpResponseFor = + end(params, RequestOptions.none()) + + /** @see end */ + @MustBeClosed + fun end(id: String, requestOptions: RequestOptions): HttpResponseFor = + end(id, SessionEndParams.none(), requestOptions) + /** * Returns a raw HTTP response for `post /v1/sessions/{id}/agentExecute`, but is otherwise * the same as [SessionService.execute]. diff --git a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt index 120519f..3150dad 100644 --- a/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt +++ b/stagehand-java-core/src/test/kotlin/com/browserbase/api/models/sessions/SessionEndParamsTest.kt @@ -18,17 +18,13 @@ internal class SessionEndParamsTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) - .body(JsonValue.from(mapOf())) + ._forceBody(JsonValue.from(mapOf())) .build() } @Test fun pathParams() { - val params = - SessionEndParams.builder() - .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .body(JsonValue.from(mapOf())) - .build() + val params = SessionEndParams.builder().id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123").build() assertThat(params._pathParam(0)).isEqualTo("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") // out-of-bound path param @@ -44,7 +40,7 @@ internal class SessionEndParamsTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) - .body(JsonValue.from(mapOf())) + ._forceBody(JsonValue.from(mapOf())) .build() val headers = params._headers() @@ -62,11 +58,7 @@ internal class SessionEndParamsTest { @Test fun headersWithoutOptionalFields() { - val params = - SessionEndParams.builder() - .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .body(JsonValue.from(mapOf())) - .build() + val params = SessionEndParams.builder().id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123").build() val headers = params._headers() @@ -82,24 +74,18 @@ internal class SessionEndParamsTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) - .body(JsonValue.from(mapOf())) + ._forceBody(JsonValue.from(mapOf())) .build() val body = params._body() - assertThat(body).isEqualTo(JsonValue.from(mapOf())) + assertThat(body.__forceBody()).isEqualTo(JsonValue.from(mapOf())) } @Test fun bodyWithoutOptionalFields() { - val params = - SessionEndParams.builder() - .id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123") - .body(JsonValue.from(mapOf())) - .build() + val params = SessionEndParams.builder().id("c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123").build() val body = params._body() - - assertThat(body).isEqualTo(JsonValue.from(mapOf())) } } 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 59dd056..8a517d2 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 @@ -121,7 +121,7 @@ internal class SessionServiceAsyncTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) - .body(JsonValue.from(mapOf())) + ._forceBody(JsonValue.from(mapOf())) .build() ) 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 ceec71b..1a94b1f 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 @@ -120,7 +120,7 @@ internal class SessionServiceTest { .xSdkVersion("3.0.6") .xSentAt(OffsetDateTime.parse("2025-01-15T10:30:00Z")) .xStreamResponse(SessionEndParams.XStreamResponse.TRUE) - .body(JsonValue.from(mapOf())) + ._forceBody(JsonValue.from(mapOf())) .build() ) From f45d473f7cc66095ead00b95a74bb6b8a9bddfe6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:47:23 +0000 Subject: [PATCH 5/6] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f4f95e8..b120b66 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 7 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-39cd9547d16412cf0568f6ce2ad8d43805dffe65bde830beeff630b903ae3b38.yml openapi_spec_hash: 9cd7c9fefa686f9711392782d948470f -config_hash: 1f709f8775e13029dc60064ef3a94355 +config_hash: 3c21550e2c94cad4339d3093d794beb0 From 4f4cec30e1e672b85ae6483f614796a2f723a16f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:47:44 +0000 Subject: [PATCH 6/6] release: 0.5.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ README.md | 10 +++++----- build.gradle.kts | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da59f99..2aca35a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.4.0" + ".": "0.5.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9b53c..10cbeca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.5.0 (2026-01-07) + +Full Changelog: [v0.4.0...v0.5.0](https://github.com/browserbase/stagehand-java/compare/v0.4.0...v0.5.0) + +### Features + +* /end endpoint returns empty object ([058cf38](https://github.com/browserbase/stagehand-java/commit/058cf3853b589356fe0b88d5899c98e4c44c8f19)) +* Added optional param to force empty object ([a93f1bf](https://github.com/browserbase/stagehand-java/commit/a93f1bf0324f4fa0b1bd96122d29534ff03cd9b2)) +* **client:** add `HttpRequest#url()` method ([5a4d99f](https://github.com/browserbase/stagehand-java/commit/5a4d99fd20473dcd6045fb65d8230c3894734364)) + + +### Documentation + +* add full working example script to readme and stagehand-java-example ([#5](https://github.com/browserbase/stagehand-java/issues/5)) ([7135947](https://github.com/browserbase/stagehand-java/commit/713594728cd301df4587bdb0337a5047695704eb)) +* prominently feature MCP server setup in root SDK readmes ([0c7cb02](https://github.com/browserbase/stagehand-java/commit/0c7cb0241f65547071fcd15e7bce41f621cfbfcb)) + ## 0.4.0 (2026-01-05) Full Changelog: [v0.3.0...v0.4.0](https://github.com/browserbase/stagehand-java/compare/v0.3.0...v0.4.0) diff --git a/README.md b/README.md index 4e80314..79c6b2b 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.4.0) -[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/0.4.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.4.0) +[![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.5.0) +[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-java/0.5.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.5.0) @@ -22,7 +22,7 @@ Use the Stagehand MCP Server to enable AI assistants to interact with this API, -The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.4.0). +The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.5.0). @@ -33,7 +33,7 @@ The REST API documentation can be found on [docs.stagehand.dev](https://docs.sta ### Gradle ```kotlin -implementation("com.browserbase.api:stagehand-java:0.4.0") +implementation("com.browserbase.api:stagehand-java:0.5.0") ``` ### Maven @@ -42,7 +42,7 @@ implementation("com.browserbase.api:stagehand-java:0.4.0") com.browserbase.api stagehand-java - 0.4.0 + 0.5.0 ``` diff --git a/build.gradle.kts b/build.gradle.kts index bd9f794..0db3fe0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.browserbase.api" - version = "0.4.0" // x-release-please-version + version = "0.5.0" // x-release-please-version } subprojects {