Skip to content
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.2.0"
".": "0.3.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 7
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-e6a9dca1a93568e403ac72128d86f30c8c3f1336d4b67017d7e61b1836f10f47.yml
openapi_spec_hash: ef01e0649bb0e283df0aa81c369649df
config_hash: 88e87ba7021be93d267ecfc8f5e6b891
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-4fb17cafc413ae3d575e3268602b01d2d0e9ebeb734a41b6086b3353ff0d2523.yml
openapi_spec_hash: 8d48d8564849246f6f14d900c6c5f60c
config_hash: 5c69fb596588b8ace08203858518c149
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.3.0 (2025-12-19)

Full Changelog: [v0.2.0...v0.3.0](https://github.com/browserbase/stagehand-kotlin/compare/v0.2.0...v0.3.0)

### Features

* **api:** manual updates ([e4a62fb](https://github.com/browserbase/stagehand-kotlin/commit/e4a62fb0ab12733c844757cbe8ab9894f6c8c8a1))
* **api:** manual updates ([9480988](https://github.com/browserbase/stagehand-kotlin/commit/9480988b36974e37584a16a2173af3072840d4b8))
* **api:** manual updates ([d0d4e1b](https://github.com/browserbase/stagehand-kotlin/commit/d0d4e1b22d4fa7a4ea49333800a4c5137659f5e6))
* **api:** manual updates ([e631708](https://github.com/browserbase/stagehand-kotlin/commit/e63170897295bc07dc47d493573c1290eb26e5f1))
* **api:** manual updates ([1697e14](https://github.com/browserbase/stagehand-kotlin/commit/1697e144099d3d39c43d1b2ad6ab830e9c4267da))


### Chores

* **internal:** codegen related update ([c6b00cb](https://github.com/browserbase/stagehand-kotlin/commit/c6b00cb1704aee4abf1aade497aed8270e250edc))
* **internal:** codegen related update ([3b4a6f0](https://github.com/browserbase/stagehand-kotlin/commit/3b4a6f0749d57d3a817722988351a2899dca5199))
* **internal:** codegen related update ([5531737](https://github.com/browserbase/stagehand-kotlin/commit/55317375b13e137f0ae5ebd5fcc05f1393bdf633))
* **internal:** codegen related update ([842e54f](https://github.com/browserbase/stagehand-kotlin/commit/842e54f32d89d64633746138a28a1947c2f7aaf4))

## 0.2.0 (2025-12-17)

Full Changelog: [v0.1.0...v0.2.0](https://github.com/browserbase/stagehand-kotlin/compare/v0.1.0...v0.2.0)
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<!-- x-release-please-start-version -->

[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-kotlin)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-kotlin/0.2.0)
[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-kotlin/0.2.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-kotlin/0.2.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.browserbase.api/stagehand-kotlin)](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-kotlin/0.3.0)
[![javadoc](https://javadoc.io/badge2/com.browserbase.api/stagehand-kotlin/0.3.0/javadoc.svg)](https://javadoc.io/doc/com.browserbase.api/stagehand-kotlin/0.3.0)

<!-- x-release-please-end -->

Expand All @@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/).

<!-- x-release-please-start-version -->

The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev). KDocs are available on [javadoc.io](https://javadoc.io/doc/com.browserbase.api/stagehand-kotlin/0.2.0).
The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev). KDocs are available on [javadoc.io](https://javadoc.io/doc/com.browserbase.api/stagehand-kotlin/0.3.0).

<!-- x-release-please-end -->

Expand All @@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.stagehand.dev](https://docs.sta
### Gradle

```kotlin
implementation("com.browserbase.api:stagehand-kotlin:0.2.0")
implementation("com.browserbase.api:stagehand-kotlin:0.3.0")
```

### Maven
Expand All @@ -33,7 +33,7 @@ implementation("com.browserbase.api:stagehand-kotlin:0.2.0")
<dependency>
<groupId>com.browserbase.api</groupId>
<artifactId>stagehand-kotlin</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```

Expand Down Expand Up @@ -234,6 +234,8 @@ The SDK throws custom unchecked exception types:
| 5xx | [`InternalServerException`](stagehand-kotlin-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt) |
| others | [`UnexpectedStatusCodeException`](stagehand-kotlin-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt) |

[`SseException`](stagehand-kotlin-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt) is thrown for errors encountered during [SSE streaming](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) after a successful initial HTTP response.

- [`StagehandIoException`](stagehand-kotlin-core/src/main/kotlin/com/browserbase/api/errors/StagehandIoException.kt): I/O networking errors.

- [`StagehandRetryableException`](stagehand-kotlin-core/src/main/kotlin/com/browserbase/api/errors/StagehandRetryableException.kt): Generic error indicating a failure that could be retried by the client.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repositories {

allprojects {
group = "com.browserbase.api"
version = "0.2.0" // x-release-please-version
version = "0.3.0" // x-release-please-version
}

subprojects {
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/stagehand.publish.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ configure<PublishingExtension> {

pom {
name.set("Stagehand API")
description.set("Stagehand SDK for AI browser automation [ALPHA].")
description.set("Stagehand SDK for AI browser automation [ALPHA]. This API allows clients to\nexecute browser automation tasks remotely on the Browserbase cloud.\n\nAll endpoints except /sessions/start require an active session ID. Responses are\nstreamed using Server-Sent Events (SSE) when the `x-stream-response: true`\nheader is provided.\n\nThis SDK is currently ALPHA software and is not production ready! Please try it\nand give us your feedback, stay tuned for upcoming release announcements!")
url.set("https://docs.stagehand.dev")

licenses {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// File generated from our OpenAPI spec by Stainless.

package com.browserbase.api.core.handlers

import com.browserbase.api.core.JsonMissing
import com.browserbase.api.core.http.HttpResponse
import com.browserbase.api.core.http.HttpResponse.Handler
import com.browserbase.api.core.http.SseMessage
import com.browserbase.api.core.http.StreamResponse
import com.browserbase.api.core.http.map
import com.browserbase.api.errors.SseException
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef

internal fun sseHandler(jsonMapper: JsonMapper): Handler<StreamResponse<SseMessage>> =
streamHandler { response, lines ->
val state = SseState(jsonMapper)
var done = false
for (line in lines) {
// Stop emitting messages, but iterate through the full stream.
if (done) {
continue
}

val message = state.decode(line) ?: continue

when {
message.data.startsWith("finished") -> {
// In this case we don't break because we still want to iterate through the full
// stream.
done = true
continue
}
message.data.startsWith("error") -> {
throw SseException.builder()
.statusCode(response.statusCode())
.headers(response.headers())
.body(
try {
jsonMapper.readValue(message.data, jacksonTypeRef())
} catch (e: Exception) {
JsonMissing.of()
}
)
.build()
}
}

if (message.event == null) {
yield(message)
}
}
}

private class SseState(
val jsonMapper: JsonMapper,
var event: String? = null,
val data: MutableList<String> = mutableListOf(),
var lastId: String? = null,
var retry: Int? = null,
) {
// https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
fun decode(line: String): SseMessage? {
if (line.isEmpty()) {
return flush()
}

if (line.startsWith(':')) {
return null
}

val fieldName: String
var value: String

val colonIndex = line.indexOf(':')
if (colonIndex == -1) {
fieldName = line
value = ""
} else {
fieldName = line.substring(0, colonIndex)
value = line.substring(colonIndex + 1)
}

if (value.startsWith(' ')) {
value = value.substring(1)
}

when (fieldName) {
"event" -> event = value
"data" -> data.add(value)
"id" -> {
if (!value.contains('\u0000')) {
lastId = value
}
}
"retry" -> value.toIntOrNull()?.let { retry = it }
}

return null
}

private fun flush(): SseMessage? {
if (isEmpty()) {
return null
}

val message =
SseMessage.builder()
.jsonMapper(jsonMapper)
.event(event)
.data(data.joinToString("\n"))
.id(lastId)
.retry(retry)
.build()

// NOTE: Per the SSE spec, do not reset lastId.
event = null
data.clear()
retry = null

return message
}

private fun isEmpty(): Boolean =
event.isNullOrEmpty() && data.isEmpty() && lastId.isNullOrEmpty() && retry == null
}

internal inline fun <reified T> Handler<StreamResponse<SseMessage>>.mapJson():
Handler<StreamResponse<T>> =
object : Handler<StreamResponse<T>> {
override fun handle(response: HttpResponse): StreamResponse<T> =
[email protected](response).map { it.json<T>() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
@file:JvmName("StreamHandler")

package com.browserbase.api.core.handlers

import com.browserbase.api.core.http.HttpResponse
import com.browserbase.api.core.http.HttpResponse.Handler
import com.browserbase.api.core.http.PhantomReachableClosingStreamResponse
import com.browserbase.api.core.http.StreamResponse
import com.browserbase.api.errors.StagehandIoException
import java.io.IOException

internal fun <T> streamHandler(
block: suspend SequenceScope<T>.(response: HttpResponse, lines: Sequence<String>) -> Unit
): Handler<StreamResponse<T>> =
object : Handler<StreamResponse<T>> {

override fun handle(response: HttpResponse): StreamResponse<T> {
val reader = response.body().bufferedReader()
val sequence =
// Wrap in a `CloseableSequence` to avoid performing a read on the `reader`
// after it has been closed, which would throw an `IOException`.
CloseableSequence(
sequence {
reader.useLines { lines ->
block(
response,
// We wrap the `lines` instead of the top-level sequence because
// we only want to catch `IOException` from the reader; not from
// the user's own code.
IOExceptionWrappingSequence(lines),
)
}
}
.constrainOnce()
)

return PhantomReachableClosingStreamResponse(
object : StreamResponse<T> {

override fun asSequence(): Sequence<T> = sequence

override fun close() {
sequence.close()
reader.close()
response.close()
}
}
)
}
}

/** A sequence that catches, wraps, and rethrows [IOException] as [StagehandIoException]. */
private class IOExceptionWrappingSequence<T>(private val sequence: Sequence<T>) : Sequence<T> {

override fun iterator(): Iterator<T> {
val iterator = sequence.iterator()
return object : Iterator<T> {

override fun next(): T =
try {
iterator.next()
} catch (e: IOException) {
throw StagehandIoException("Stream failed", e)
}

override fun hasNext(): Boolean =
try {
iterator.hasNext()
} catch (e: IOException) {
throw StagehandIoException("Stream failed", e)
}
}
}
}

/**
* A sequence that can be closed.
*
* Once [close] is called, it will not yield more elements. It will also no longer consult the
* underlying [Iterator.hasNext] method.
*/
private class CloseableSequence<T>(private val sequence: Sequence<T>) : Sequence<T> {

private var isClosed: Boolean = false

override fun iterator(): Iterator<T> {
val iterator = sequence.iterator()
return object : Iterator<T> {

override fun next(): T = iterator.next()

override fun hasNext(): Boolean = !isClosed && iterator.hasNext()
}
}

fun close() {
isClosed = true
}
}
Loading
Loading