diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 10f3091..6b7b74c 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.2.0"
+ ".": "0.3.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 36782ae..5a89fb7 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-705638ac8966569986bd9ebb7c9761bf0016909e9f2753e77ceabb12c8049511.yml
-openapi_spec_hash: a8fbbcaa38e91c7f97313620b42d8d62
-config_hash: a35b56eb05306a0f02e83c11d57f975f
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-ed52466945f2f8dfd3814a29e948d7bf30af7b76a7a7689079c03b8baf64e26f.yml
+openapi_spec_hash: 5d57aaf2362b0d882372dbf76477ba23
+config_hash: 989ddfee371586e9156b4d484ec0a6cc
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04011d6..81e4589 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
# Changelog
+## 0.3.0 (2025-12-23)
+
+Full Changelog: [v0.2.0...v0.3.0](https://github.com/browserbase/stagehand-java/compare/v0.2.0...v0.3.0)
+
+### Features
+
+* [STG-1053] [server] Use fastify-zod-openapi + zod v4 for openapi generation ([9de17a2](https://github.com/browserbase/stagehand-java/commit/9de17a2552f759f6e8f37de4c3b9bb5d403edcc4))
+* **api:** manual updates ([d9e462c](https://github.com/browserbase/stagehand-java/commit/d9e462c24ed6d3e165a5873e7f70f011d768500a))
+* **api:** manual updates ([b6bdf1a](https://github.com/browserbase/stagehand-java/commit/b6bdf1a26a45556dcfee59b088983cd7e7839f43))
+* **api:** manual updates ([9b6860a](https://github.com/browserbase/stagehand-java/commit/9b6860a4f60d1a740fbb026eef291e40fb8e1845))
+* **api:** manual updates ([28f7978](https://github.com/browserbase/stagehand-java/commit/28f7978e1f040169dbb20aa68efb8e5ceb9a34d2))
+* **api:** manual updates ([07db616](https://github.com/browserbase/stagehand-java/commit/07db616b7209716efa346035d65f5d7bf616c992))
+* **api:** manual updates ([3cf2073](https://github.com/browserbase/stagehand-java/commit/3cf20739785134610aa7bcbdbce3215c3c3a9970))
+* **api:** manual updates ([adace53](https://github.com/browserbase/stagehand-java/commit/adace53fc3e4a0fb4b866172aec98519ec0f29d5))
+* **api:** manual updates ([dd65420](https://github.com/browserbase/stagehand-java/commit/dd65420fecb521fba36cbf2a623ce675973a27a0))
+* **api:** manual updates ([067c9e6](https://github.com/browserbase/stagehand-java/commit/067c9e644940fa3671665a902bc605b8e358473f))
+* **api:** manual updates ([d7aabe3](https://github.com/browserbase/stagehand-java/commit/d7aabe36f42d8344477f2f468bd3ba15837c0516))
+* **api:** manual updates ([13074f1](https://github.com/browserbase/stagehand-java/commit/13074f11bbc12057b60e89f37f850ca8f5bc7a83))
+
+
+### Documentation
+
+* add more examples ([54eba34](https://github.com/browserbase/stagehand-java/commit/54eba3455dbfc5dd61c1c3b88aedd34c56bce37a))
+
## 0.2.0 (2025-12-16)
Full Changelog: [v0.1.0...v0.2.0](https://github.com/browserbase/stagehand-java/compare/v0.1.0...v0.2.0)
diff --git a/README.md b/README.md
index c383ecb..ab416ca 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/0.2.0)
-[](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.2.0)
+[](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/0.3.0)
+[](https://javadoc.io/doc/com.browserbase.api/stagehand-java/0.3.0)
@@ -13,7 +13,7 @@ It is generated with [Stainless](https://www.stainless.com/).
-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.2.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.3.0).
@@ -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-java:0.2.0")
+implementation("com.browserbase.api:stagehand-java:0.3.0")
```
### Maven
@@ -33,7 +33,7 @@ implementation("com.browserbase.api:stagehand-java:0.2.0")
com.browserbase.api
stagehand-java
- 0.2.0
+ 0.3.0
```
@@ -56,7 +56,7 @@ import com.browserbase.api.models.sessions.SessionActResponse;
StagehandClient client = StagehandOkHttpClient.fromEnv();
SessionActParams params = SessionActParams.builder()
- .sessionId("00000000-your-session-id-000000000000")
+ .id("00000000-your-session-id-000000000000")
.input("click the first link on the page")
.build();
SessionActResponse response = client.sessions().act(params);
@@ -162,7 +162,7 @@ import java.util.concurrent.CompletableFuture;
StagehandClient client = StagehandOkHttpClient.fromEnv();
SessionActParams params = SessionActParams.builder()
- .sessionId("00000000-your-session-id-000000000000")
+ .id("00000000-your-session-id-000000000000")
.input("click the first link on the page")
.build();
CompletableFuture response = client.async().sessions().act(params);
@@ -182,7 +182,7 @@ import java.util.concurrent.CompletableFuture;
StagehandClientAsync client = StagehandOkHttpClientAsync.fromEnv();
SessionActParams params = SessionActParams.builder()
- .sessionId("00000000-your-session-id-000000000000")
+ .id("00000000-your-session-id-000000000000")
.input("click the first link on the page")
.build();
CompletableFuture response = client.sessions().act(params);
@@ -190,6 +190,98 @@ CompletableFuture response = client.sessions().act(params);
The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
+## Streaming
+
+The SDK defines methods that return response "chunk" streams, where each chunk can be individually processed as soon as it arrives instead of waiting on the full response. Streaming methods generally correspond to [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) or [JSONL](https://jsonlines.org) responses.
+
+Some of these methods may have streaming and non-streaming variants, but a streaming method will always have a `Streaming` suffix in its name, even if it doesn't have a non-streaming variant.
+
+These streaming methods return [`StreamResponse`](stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/StreamResponse.kt) for synchronous clients:
+
+```java
+import com.browserbase.api.core.http.StreamResponse;
+import com.browserbase.api.models.sessions.StreamEvent;
+
+try (StreamResponse streamResponse = client.sessions().actStreaming(params)) {
+ streamResponse.stream().forEach(chunk -> {
+ System.out.println(chunk);
+ });
+ System.out.println("No more chunks!");
+}
+```
+
+Or [`AsyncStreamResponse`](stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/AsyncStreamResponse.kt) for asynchronous clients:
+
+```java
+import com.browserbase.api.core.http.AsyncStreamResponse;
+import com.browserbase.api.models.sessions.StreamEvent;
+import java.util.Optional;
+
+client.async().sessions().actStreaming(params).subscribe(chunk -> {
+ System.out.println(chunk);
+});
+
+// If you need to handle errors or completion of the stream
+client.async().sessions().actStreaming(params).subscribe(new AsyncStreamResponse.Handler<>() {
+ @Override
+ public void onNext(StreamEvent chunk) {
+ System.out.println(chunk);
+ }
+
+ @Override
+ public void onComplete(Optional error) {
+ if (error.isPresent()) {
+ System.out.println("Something went wrong!");
+ throw new RuntimeException(error.get());
+ } else {
+ System.out.println("No more chunks!");
+ }
+ }
+});
+
+// Or use futures
+client.async().sessions().actStreaming(params)
+ .subscribe(chunk -> {
+ System.out.println(chunk);
+ })
+ .onCompleteFuture();
+ .whenComplete((unused, error) -> {
+ if (error != null) {
+ System.out.println("Something went wrong!");
+ throw new RuntimeException(error);
+ } else {
+ System.out.println("No more chunks!");
+ }
+ });
+```
+
+Async streaming uses a dedicated per-client cached thread pool [`Executor`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html) to stream without blocking the current thread. This default is suitable for most purposes.
+
+To use a different `Executor`, configure the subscription using the `executor` parameter:
+
+```java
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+Executor executor = Executors.newFixedThreadPool(4);
+client.async().sessions().actStreaming(params).subscribe(
+ chunk -> System.out.println(chunk), executor
+);
+```
+
+Or configure the client globally using the `streamHandlerExecutor` method:
+
+```java
+import com.browserbase.api.client.StagehandClient;
+import com.browserbase.api.client.okhttp.StagehandOkHttpClient;
+import java.util.concurrent.Executors;
+
+StagehandClient client = StagehandOkHttpClient.builder()
+ .fromEnv()
+ .streamHandlerExecutor(Executors.newFixedThreadPool(4))
+ .build();
+```
+
## Raw responses
The SDK defines methods that deserialize responses into instances of Java classes. However, these methods don't provide access to the response headers, status code, or the raw response body.
@@ -203,8 +295,7 @@ import com.browserbase.api.models.sessions.SessionStartParams;
import com.browserbase.api.models.sessions.SessionStartResponse;
SessionStartParams params = SessionStartParams.builder()
- .browserbaseApiKey("your Browserbase API key")
- .browserbaseProjectId("your Browserbase Project ID")
+ .modelName("openai/gpt-5-nano")
.build();
HttpResponseFor response = client.sessions().withRawResponse().start(params);
@@ -237,6 +328,8 @@ The SDK throws custom unchecked exception types:
| 5xx | [`InternalServerException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt) |
| others | [`UnexpectedStatusCodeException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt) |
+ [`SseException`](stagehand-java-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-java-core/src/main/kotlin/com/browserbase/api/errors/StagehandIoException.kt): I/O networking errors.
- [`StagehandRetryableException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/StagehandRetryableException.kt): Generic error indicating a failure that could be retried by the client.
@@ -505,8 +598,8 @@ import com.browserbase.api.core.JsonMissing;
import com.browserbase.api.models.sessions.SessionActParams;
SessionActParams params = SessionActParams.builder()
- .input("click the sign in button")
- .sessionId(JsonMissing.of())
+ .input("Click the login button")
+ .id(JsonMissing.of())
.build();
```
diff --git a/build.gradle.kts b/build.gradle.kts
index 63a11b4..d06f0a9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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 {
diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt
index 8955635..40010ee 100644
--- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt
+++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt
@@ -7,6 +7,7 @@ import com.browserbase.api.client.StagehandClientImpl
import com.browserbase.api.core.ClientOptions
import com.browserbase.api.core.Sleeper
import com.browserbase.api.core.Timeout
+import com.browserbase.api.core.http.AsyncStreamResponse
import com.browserbase.api.core.http.Headers
import com.browserbase.api.core.http.HttpClient
import com.browserbase.api.core.http.QueryParams
@@ -16,6 +17,7 @@ import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
@@ -121,6 +123,17 @@ class StagehandOkHttpClient private constructor() {
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
/**
* The interface to use for delaying execution, like during retries.
*
diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt
index 2cb208d..33ce68f 100644
--- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt
+++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt
@@ -7,6 +7,7 @@ import com.browserbase.api.client.StagehandClientAsyncImpl
import com.browserbase.api.core.ClientOptions
import com.browserbase.api.core.Sleeper
import com.browserbase.api.core.Timeout
+import com.browserbase.api.core.http.AsyncStreamResponse
import com.browserbase.api.core.http.Headers
import com.browserbase.api.core.http.HttpClient
import com.browserbase.api.core.http.QueryParams
@@ -16,6 +17,7 @@ import java.net.Proxy
import java.time.Clock
import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
@@ -121,6 +123,17 @@ class StagehandOkHttpClientAsync private constructor() {
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
/**
* The interface to use for delaying execution, like during retries.
*
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt
index 8692cb0..2da3f9a 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt
@@ -2,6 +2,7 @@
package com.browserbase.api.core
+import com.browserbase.api.core.http.AsyncStreamResponse
import com.browserbase.api.core.http.Headers
import com.browserbase.api.core.http.HttpClient
import com.browserbase.api.core.http.PhantomReachableClosingHttpClient
@@ -11,6 +12,11 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import java.time.Clock
import java.time.Duration
import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicLong
import kotlin.jvm.optionals.getOrNull
/** A class representing the SDK client configuration. */
@@ -40,6 +46,14 @@ private constructor(
* rarely needs to be overridden.
*/
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
/**
* The interface to use for delaying execution, like during retries.
*
@@ -147,6 +161,7 @@ private constructor(
private var httpClient: HttpClient? = null
private var checkJacksonVersionCompatibility: Boolean = true
private var jsonMapper: JsonMapper = jsonMapper()
+ private var streamHandlerExecutor: Executor? = null
private var sleeper: Sleeper? = null
private var clock: Clock = Clock.systemUTC()
private var baseUrl: String? = null
@@ -164,6 +179,7 @@ private constructor(
httpClient = clientOptions.originalHttpClient
checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
jsonMapper = clientOptions.jsonMapper
+ streamHandlerExecutor = clientOptions.streamHandlerExecutor
sleeper = clientOptions.sleeper
clock = clientOptions.clock
baseUrl = clientOptions.baseUrl
@@ -207,6 +223,20 @@ private constructor(
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor =
+ if (streamHandlerExecutor is ExecutorService)
+ PhantomReachableExecutorService(streamHandlerExecutor)
+ else streamHandlerExecutor
+ }
+
/**
* The interface to use for delaying execution, like during retries.
*
@@ -422,6 +452,24 @@ private constructor(
*/
fun build(): ClientOptions {
val httpClient = checkRequired("httpClient", httpClient)
+ val streamHandlerExecutor =
+ streamHandlerExecutor
+ ?: PhantomReachableExecutorService(
+ Executors.newCachedThreadPool(
+ object : ThreadFactory {
+
+ private val threadFactory: ThreadFactory =
+ Executors.defaultThreadFactory()
+ private val count = AtomicLong(0)
+
+ override fun newThread(runnable: Runnable): Thread =
+ threadFactory.newThread(runnable).also {
+ it.name =
+ "stagehand-stream-handler-thread-${count.getAndIncrement()}"
+ }
+ }
+ )
+ )
val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper())
val browserbaseApiKey = checkRequired("browserbaseApiKey", browserbaseApiKey)
val browserbaseProjectId = checkRequired("browserbaseProjectId", browserbaseProjectId)
@@ -464,6 +512,7 @@ private constructor(
.build(),
checkJacksonVersionCompatibility,
jsonMapper,
+ streamHandlerExecutor,
sleeper,
clock,
baseUrl,
@@ -491,6 +540,7 @@ private constructor(
*/
fun close() {
httpClient.close()
+ (streamHandlerExecutor as? ExecutorService)?.shutdown()
sleeper.close()
}
}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/SseHandler.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/SseHandler.kt
new file mode 100644
index 0000000..039faae
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/SseHandler.kt
@@ -0,0 +1,137 @@
+// File generated from our OpenAPI spec by Stainless.
+
+@file:JvmName("SseHandler")
+
+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
+
+@JvmSynthetic
+internal fun sseHandler(jsonMapper: JsonMapper): Handler> =
+ 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 = 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
+}
+
+@JvmSynthetic
+internal inline fun Handler>.mapJson():
+ Handler> =
+ object : Handler> {
+ override fun handle(response: HttpResponse): StreamResponse =
+ this@mapJson.handle(response).map { it.json() }
+ }
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/StreamHandler.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/StreamHandler.kt
new file mode 100644
index 0000000..4cd4f25
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/handlers/StreamHandler.kt
@@ -0,0 +1,102 @@
+@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
+import java.util.stream.Stream
+import kotlin.streams.asStream
+
+@JvmSynthetic
+internal fun streamHandler(
+ block: suspend SequenceScope.(response: HttpResponse, lines: Sequence) -> Unit
+): Handler> =
+ object : Handler> {
+
+ override fun handle(response: HttpResponse): StreamResponse {
+ 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 {
+
+ override fun stream(): Stream = sequence.asStream()
+
+ override fun close() {
+ sequence.close()
+ reader.close()
+ response.close()
+ }
+ }
+ )
+ }
+ }
+
+/** A sequence that catches, wraps, and rethrows [IOException] as [StagehandIoException]. */
+private class IOExceptionWrappingSequence(private val sequence: Sequence) : Sequence {
+
+ override fun iterator(): Iterator {
+ val iterator = sequence.iterator()
+ return object : Iterator {
+
+ 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(private val sequence: Sequence) : Sequence {
+
+ private var isClosed: Boolean = false
+
+ override fun iterator(): Iterator {
+ val iterator = sequence.iterator()
+ return object : Iterator {
+
+ override fun next(): T = iterator.next()
+
+ override fun hasNext(): Boolean = !isClosed && iterator.hasNext()
+ }
+ }
+
+ fun close() {
+ isClosed = true
+ }
+}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/SseMessage.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/SseMessage.kt
new file mode 100644
index 0000000..21cc44e
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/SseMessage.kt
@@ -0,0 +1,74 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.browserbase.api.core.http
+
+import com.browserbase.api.errors.StagehandInvalidDataException
+import com.fasterxml.jackson.databind.json.JsonMapper
+import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
+import java.util.Objects
+
+internal class SseMessage
+private constructor(
+ val jsonMapper: JsonMapper,
+ val event: String?,
+ val data: String,
+ val id: String?,
+ val retry: Int?,
+) {
+
+ companion object {
+ @JvmStatic fun builder() = Builder()
+ }
+
+ class Builder internal constructor() {
+
+ private var jsonMapper: JsonMapper? = null
+ private var event: String? = null
+ private var data: String = ""
+ private var id: String? = null
+ private var retry: Int? = null
+
+ fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
+
+ fun event(event: String?) = apply { this.event = event }
+
+ fun data(data: String) = apply { this.data = data }
+
+ fun id(id: String?) = apply { this.id = id }
+
+ fun retry(retry: Int?) = apply { this.retry = retry }
+
+ fun build(): SseMessage = SseMessage(jsonMapper!!, event, data, id, retry)
+ }
+
+ inline fun json(): T =
+ try {
+ jsonMapper.readerFor(jacksonTypeRef()).readValue(jsonNode)
+ } catch (e: Exception) {
+ throw StagehandInvalidDataException("Error reading response", e)
+ }
+
+ private val jsonNode by lazy {
+ try {
+ jsonMapper.readTree(data)
+ } catch (e: Exception) {
+ throw StagehandInvalidDataException("Error reading response", e)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is SseMessage &&
+ event == other.event &&
+ data == other.data &&
+ id == other.id &&
+ retry == other.retry
+ }
+
+ override fun hashCode(): Int = Objects.hash(event, data, id, retry)
+
+ override fun toString(): String = "SseMessage{event=$event, data=$data, id=$id, retry=$retry}"
+}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt
new file mode 100644
index 0000000..0dfdd2f
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/SseException.kt
@@ -0,0 +1,91 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.browserbase.api.errors
+
+import com.browserbase.api.core.JsonValue
+import com.browserbase.api.core.checkRequired
+import com.browserbase.api.core.http.Headers
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
+
+class SseException
+private constructor(
+ private val statusCode: Int,
+ private val headers: Headers,
+ private val body: JsonValue,
+ cause: Throwable?,
+) : StagehandServiceException("$statusCode: $body", cause) {
+
+ override fun statusCode(): Int = statusCode
+
+ override fun headers(): Headers = headers
+
+ override fun body(): JsonValue = body
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /**
+ * Returns a mutable builder for constructing an instance of [SseException].
+ *
+ * The following fields are required:
+ * ```java
+ * .statusCode()
+ * .headers()
+ * .body()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [SseException]. */
+ class Builder internal constructor() {
+
+ private var statusCode: Int? = null
+ private var headers: Headers? = null
+ private var body: JsonValue? = null
+ private var cause: Throwable? = null
+
+ @JvmSynthetic
+ internal fun from(sseException: SseException) = apply {
+ statusCode = sseException.statusCode
+ headers = sseException.headers
+ body = sseException.body
+ cause = sseException.cause
+ }
+
+ fun statusCode(statusCode: Int) = apply { this.statusCode = statusCode }
+
+ fun headers(headers: Headers) = apply { this.headers = headers }
+
+ fun body(body: JsonValue) = apply { this.body = body }
+
+ fun cause(cause: Throwable?) = apply { this.cause = cause }
+
+ /** Alias for calling [Builder.cause] with `cause.orElse(null)`. */
+ fun cause(cause: Optional) = cause(cause.getOrNull())
+
+ /**
+ * Returns an immutable instance of [SseException].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .statusCode()
+ * .headers()
+ * .body()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): SseException =
+ SseException(
+ checkRequired("statusCode", statusCode),
+ checkRequired("headers", headers),
+ checkRequired("body", body),
+ cause,
+ )
+ }
+}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt
index d30a40b..2ce760b 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/Action.kt
@@ -19,39 +19,32 @@ import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
+/** Action object returned by observe and used by act */
class Action
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
- private val arguments: JsonField>,
private val description: JsonField,
- private val method: JsonField,
private val selector: JsonField,
- private val backendNodeId: JsonField,
+ private val arguments: JsonField>,
+ private val backendNodeId: JsonField,
+ private val method: JsonField,
private val additionalProperties: MutableMap,
) {
@JsonCreator
private constructor(
- @JsonProperty("arguments")
- @ExcludeMissing
- arguments: JsonField> = JsonMissing.of(),
@JsonProperty("description")
@ExcludeMissing
description: JsonField = JsonMissing.of(),
- @JsonProperty("method") @ExcludeMissing method: JsonField = JsonMissing.of(),
@JsonProperty("selector") @ExcludeMissing selector: JsonField = JsonMissing.of(),
+ @JsonProperty("arguments")
+ @ExcludeMissing
+ arguments: JsonField> = JsonMissing.of(),
@JsonProperty("backendNodeId")
@ExcludeMissing
- backendNodeId: JsonField = JsonMissing.of(),
- ) : this(arguments, description, method, selector, backendNodeId, mutableMapOf())
-
- /**
- * Arguments for the method
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
- * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
- */
- fun arguments(): List = arguments.getRequired("arguments")
+ backendNodeId: JsonField = JsonMissing.of(),
+ @JsonProperty("method") @ExcludeMissing method: JsonField = JsonMissing.of(),
+ ) : this(description, selector, arguments, backendNodeId, method, mutableMapOf())
/**
* Human-readable description of the action
@@ -62,35 +55,36 @@ private constructor(
fun description(): String = description.getRequired("description")
/**
- * Method to execute (e.g., "click", "fill")
+ * CSS selector or XPath for the element
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
*/
- fun method(): String = method.getRequired("method")
+ fun selector(): String = selector.getRequired("selector")
/**
- * CSS or XPath selector for the element
+ * Arguments to pass to the method
*
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
- * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
*/
- fun selector(): String = selector.getRequired("selector")
+ fun arguments(): Optional> = arguments.getOptional("arguments")
/**
- * CDP backend node ID
+ * Backend node ID for the element
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
*/
- fun backendNodeId(): Optional = backendNodeId.getOptional("backendNodeId")
+ fun backendNodeId(): Optional = backendNodeId.getOptional("backendNodeId")
/**
- * Returns the raw JSON value of [arguments].
+ * The method to execute (click, fill, etc.)
*
- * Unlike [arguments], this method doesn't throw if the JSON field has an unexpected type.
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
+ * server responded with an unexpected value).
*/
- @JsonProperty("arguments") @ExcludeMissing fun _arguments(): JsonField> = arguments
+ fun method(): Optional = method.getOptional("method")
/**
* Returns the raw JSON value of [description].
@@ -100,18 +94,18 @@ private constructor(
@JsonProperty("description") @ExcludeMissing fun _description(): JsonField = description
/**
- * Returns the raw JSON value of [method].
+ * Returns the raw JSON value of [selector].
*
- * Unlike [method], this method doesn't throw if the JSON field has an unexpected type.
+ * Unlike [selector], this method doesn't throw if the JSON field has an unexpected type.
*/
- @JsonProperty("method") @ExcludeMissing fun _method(): JsonField = method
+ @JsonProperty("selector") @ExcludeMissing fun _selector(): JsonField = selector
/**
- * Returns the raw JSON value of [selector].
+ * Returns the raw JSON value of [arguments].
*
- * Unlike [selector], this method doesn't throw if the JSON field has an unexpected type.
+ * Unlike [arguments], this method doesn't throw if the JSON field has an unexpected type.
*/
- @JsonProperty("selector") @ExcludeMissing fun _selector(): JsonField = selector
+ @JsonProperty("arguments") @ExcludeMissing fun _arguments(): JsonField> = arguments
/**
* Returns the raw JSON value of [backendNodeId].
@@ -120,7 +114,14 @@ private constructor(
*/
@JsonProperty("backendNodeId")
@ExcludeMissing
- fun _backendNodeId(): JsonField = backendNodeId
+ fun _backendNodeId(): JsonField = backendNodeId
+
+ /**
+ * Returns the raw JSON value of [method].
+ *
+ * Unlike [method], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("method") @ExcludeMissing fun _method(): JsonField = method
@JsonAnySetter
private fun putAdditionalProperty(key: String, value: JsonValue) {
@@ -141,9 +142,7 @@ private constructor(
*
* The following fields are required:
* ```java
- * .arguments()
* .description()
- * .method()
* .selector()
* ```
*/
@@ -153,24 +152,47 @@ private constructor(
/** A builder for [Action]. */
class Builder internal constructor() {
- private var arguments: JsonField>? = null
private var description: JsonField? = null
- private var method: JsonField? = null
private var selector: JsonField? = null
- private var backendNodeId: JsonField = JsonMissing.of()
+ private var arguments: JsonField>? = null
+ private var backendNodeId: JsonField = JsonMissing.of()
+ private var method: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@JvmSynthetic
internal fun from(action: Action) = apply {
- arguments = action.arguments.map { it.toMutableList() }
description = action.description
- method = action.method
selector = action.selector
+ arguments = action.arguments.map { it.toMutableList() }
backendNodeId = action.backendNodeId
+ method = action.method
additionalProperties = action.additionalProperties.toMutableMap()
}
- /** Arguments for the method */
+ /** Human-readable description of the action */
+ fun description(description: String) = description(JsonField.of(description))
+
+ /**
+ * Sets [Builder.description] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.description] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun description(description: JsonField) = apply { this.description = description }
+
+ /** CSS selector or XPath for the element */
+ fun selector(selector: String) = selector(JsonField.of(selector))
+
+ /**
+ * Sets [Builder.selector] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.selector] with a well-typed [String] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported value.
+ */
+ fun selector(selector: JsonField) = apply { this.selector = selector }
+
+ /** Arguments to pass to the method */
fun arguments(arguments: List) = arguments(JsonField.of(arguments))
/**
@@ -196,19 +218,21 @@ private constructor(
}
}
- /** Human-readable description of the action */
- fun description(description: String) = description(JsonField.of(description))
+ /** Backend node ID for the element */
+ fun backendNodeId(backendNodeId: Double) = backendNodeId(JsonField.of(backendNodeId))
/**
- * Sets [Builder.description] to an arbitrary JSON value.
+ * Sets [Builder.backendNodeId] to an arbitrary JSON value.
*
- * You should usually call [Builder.description] with a well-typed [String] value instead.
+ * You should usually call [Builder.backendNodeId] with a well-typed [Double] value instead.
* This method is primarily for setting the field to an undocumented or not yet supported
* value.
*/
- fun description(description: JsonField) = apply { this.description = description }
+ fun backendNodeId(backendNodeId: JsonField) = apply {
+ this.backendNodeId = backendNodeId
+ }
- /** Method to execute (e.g., "click", "fill") */
+ /** The method to execute (click, fill, etc.) */
fun method(method: String) = method(JsonField.of(method))
/**
@@ -219,31 +243,6 @@ private constructor(
*/
fun method(method: JsonField) = apply { this.method = method }
- /** CSS or XPath selector for the element */
- fun selector(selector: String) = selector(JsonField.of(selector))
-
- /**
- * Sets [Builder.selector] to an arbitrary JSON value.
- *
- * You should usually call [Builder.selector] with a well-typed [String] value instead. This
- * method is primarily for setting the field to an undocumented or not yet supported value.
- */
- fun selector(selector: JsonField) = apply { this.selector = selector }
-
- /** CDP backend node ID */
- fun backendNodeId(backendNodeId: Long) = backendNodeId(JsonField.of(backendNodeId))
-
- /**
- * Sets [Builder.backendNodeId] to an arbitrary JSON value.
- *
- * You should usually call [Builder.backendNodeId] with a well-typed [Long] value instead.
- * This method is primarily for setting the field to an undocumented or not yet supported
- * value.
- */
- fun backendNodeId(backendNodeId: JsonField) = apply {
- this.backendNodeId = backendNodeId
- }
-
fun additionalProperties(additionalProperties: Map) = apply {
this.additionalProperties.clear()
putAllAdditionalProperties(additionalProperties)
@@ -270,9 +269,7 @@ private constructor(
*
* The following fields are required:
* ```java
- * .arguments()
* .description()
- * .method()
* .selector()
* ```
*
@@ -280,11 +277,11 @@ private constructor(
*/
fun build(): Action =
Action(
- checkRequired("arguments", arguments).map { it.toImmutable() },
checkRequired("description", description),
- checkRequired("method", method),
checkRequired("selector", selector),
+ (arguments ?: JsonMissing.of()).map { it.toImmutable() },
backendNodeId,
+ method,
additionalProperties.toMutableMap(),
)
}
@@ -296,11 +293,11 @@ private constructor(
return@apply
}
- arguments()
description()
- method()
selector()
+ arguments()
backendNodeId()
+ method()
validated = true
}
@@ -319,11 +316,11 @@ private constructor(
*/
@JvmSynthetic
internal fun validity(): Int =
- (arguments.asKnown().getOrNull()?.size ?: 0) +
- (if (description.asKnown().isPresent) 1 else 0) +
- (if (method.asKnown().isPresent) 1 else 0) +
+ (if (description.asKnown().isPresent) 1 else 0) +
(if (selector.asKnown().isPresent) 1 else 0) +
- (if (backendNodeId.asKnown().isPresent) 1 else 0)
+ (arguments.asKnown().getOrNull()?.size ?: 0) +
+ (if (backendNodeId.asKnown().isPresent) 1 else 0) +
+ (if (method.asKnown().isPresent) 1 else 0)
override fun equals(other: Any?): Boolean {
if (this === other) {
@@ -331,20 +328,20 @@ private constructor(
}
return other is Action &&
- arguments == other.arguments &&
description == other.description &&
- method == other.method &&
selector == other.selector &&
+ arguments == other.arguments &&
backendNodeId == other.backendNodeId &&
+ method == other.method &&
additionalProperties == other.additionalProperties
}
private val hashCode: Int by lazy {
- Objects.hash(arguments, description, method, selector, backendNodeId, additionalProperties)
+ Objects.hash(description, selector, arguments, backendNodeId, method, additionalProperties)
}
override fun hashCode(): Int = hashCode
override fun toString() =
- "Action{arguments=$arguments, description=$description, method=$method, selector=$selector, backendNodeId=$backendNodeId, additionalProperties=$additionalProperties}"
+ "Action{description=$description, selector=$selector, arguments=$arguments, backendNodeId=$backendNodeId, method=$method, additionalProperties=$additionalProperties}"
}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt
index e08ebfa..bfbf0aa 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/ModelConfig.kt
@@ -2,340 +2,449 @@
package com.browserbase.api.models.sessions
+import com.browserbase.api.core.BaseDeserializer
+import com.browserbase.api.core.BaseSerializer
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.allMaxBy
+import com.browserbase.api.core.checkRequired
+import com.browserbase.api.core.getOrThrow
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 com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.ObjectCodec
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.annotation.JsonSerialize
+import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import java.util.Collections
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
+/**
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano', 'anthropic/claude-4.5-opus')
+ */
+@JsonDeserialize(using = ModelConfig.Deserializer::class)
+@JsonSerialize(using = ModelConfig.Serializer::class)
class ModelConfig
-@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
- private val apiKey: JsonField,
- private val baseUrl: JsonField,
- private val model: JsonField,
- private val provider: JsonField,
- private val additionalProperties: MutableMap,
+ private val name: String? = null,
+ private val modelConfigObject: ModelConfigObject? = null,
+ private val _json: JsonValue? = null,
) {
- @JsonCreator
- private constructor(
- @JsonProperty("apiKey") @ExcludeMissing apiKey: JsonField = JsonMissing.of(),
- @JsonProperty("baseURL") @ExcludeMissing baseUrl: JsonField = JsonMissing.of(),
- @JsonProperty("model") @ExcludeMissing model: JsonField = JsonMissing.of(),
- @JsonProperty("provider") @ExcludeMissing provider: JsonField = JsonMissing.of(),
- ) : this(apiKey, baseUrl, model, provider, mutableMapOf())
-
/**
- * API key for the model provider
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
*/
- fun apiKey(): Optional = apiKey.getOptional("apiKey")
+ fun name(): Optional = Optional.ofNullable(name)
- /**
- * Custom base URL for API
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
- */
- fun baseUrl(): Optional = baseUrl.getOptional("baseURL")
+ fun modelConfigObject(): Optional = Optional.ofNullable(modelConfigObject)
- /**
- * Model name
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
- */
- fun model(): Optional = model.getOptional("model")
+ fun isName(): Boolean = name != null
- /**
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
- */
- fun provider(): Optional = provider.getOptional("provider")
+ fun isModelConfigObject(): Boolean = modelConfigObject != null
/**
- * Returns the raw JSON value of [apiKey].
- *
- * Unlike [apiKey], this method doesn't throw if the JSON field has an unexpected type.
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
*/
- @JsonProperty("apiKey") @ExcludeMissing fun _apiKey(): JsonField = apiKey
+ fun asName(): String = name.getOrThrow("name")
- /**
- * Returns the raw JSON value of [baseUrl].
- *
- * Unlike [baseUrl], this method doesn't throw if the JSON field has an unexpected type.
- */
- @JsonProperty("baseURL") @ExcludeMissing fun _baseUrl(): JsonField = baseUrl
+ fun asModelConfigObject(): ModelConfigObject = modelConfigObject.getOrThrow("modelConfigObject")
- /**
- * Returns the raw JSON value of [model].
- *
- * Unlike [model], this method doesn't throw if the JSON field has an unexpected type.
- */
- @JsonProperty("model") @ExcludeMissing fun _model(): JsonField = model
+ fun _json(): Optional = Optional.ofNullable(_json)
+
+ fun accept(visitor: Visitor): T =
+ when {
+ name != null -> visitor.visitName(name)
+ modelConfigObject != null -> visitor.visitModelConfigObject(modelConfigObject)
+ else -> visitor.unknown(_json)
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): ModelConfig = apply {
+ if (validated) {
+ return@apply
+ }
+
+ accept(
+ object : Visitor {
+ override fun visitName(name: String) {}
+
+ override fun visitModelConfigObject(modelConfigObject: ModelConfigObject) {
+ modelConfigObject.validate()
+ }
+ }
+ )
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: StagehandInvalidDataException) {
+ false
+ }
/**
- * Returns the raw JSON value of [provider].
+ * Returns a score indicating how many valid values are contained in this object recursively.
*
- * Unlike [provider], this method doesn't throw if the JSON field has an unexpected type.
+ * Used for best match union deserialization.
*/
- @JsonProperty("provider") @ExcludeMissing fun _provider(): JsonField = provider
-
- @JsonAnySetter
- private fun putAdditionalProperty(key: String, value: JsonValue) {
- additionalProperties.put(key, value)
- }
+ @JvmSynthetic
+ internal fun validity(): Int =
+ accept(
+ object : Visitor {
+ override fun visitName(name: String) = 1
- @JsonAnyGetter
- @ExcludeMissing
- fun _additionalProperties(): Map =
- Collections.unmodifiableMap(additionalProperties)
+ override fun visitModelConfigObject(modelConfigObject: ModelConfigObject) =
+ modelConfigObject.validity()
- fun toBuilder() = Builder().from(this)
+ override fun unknown(json: JsonValue?) = 0
+ }
+ )
- companion object {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
- /** Returns a mutable builder for constructing an instance of [ModelConfig]. */
- @JvmStatic fun builder() = Builder()
+ return other is ModelConfig &&
+ name == other.name &&
+ modelConfigObject == other.modelConfigObject
}
- /** A builder for [ModelConfig]. */
- class Builder internal constructor() {
-
- private var apiKey: JsonField = JsonMissing.of()
- private var baseUrl: JsonField = JsonMissing.of()
- private var model: JsonField = JsonMissing.of()
- private var provider: JsonField = JsonMissing.of()
- private var additionalProperties: MutableMap = mutableMapOf()
+ override fun hashCode(): Int = Objects.hash(name, modelConfigObject)
- @JvmSynthetic
- internal fun from(modelConfig: ModelConfig) = apply {
- apiKey = modelConfig.apiKey
- baseUrl = modelConfig.baseUrl
- model = modelConfig.model
- provider = modelConfig.provider
- additionalProperties = modelConfig.additionalProperties.toMutableMap()
+ override fun toString(): String =
+ when {
+ name != null -> "ModelConfig{name=$name}"
+ modelConfigObject != null -> "ModelConfig{modelConfigObject=$modelConfigObject}"
+ _json != null -> "ModelConfig{_unknown=$_json}"
+ else -> throw IllegalStateException("Invalid ModelConfig")
}
- /** API key for the model provider */
- fun apiKey(apiKey: String) = apiKey(JsonField.of(apiKey))
+ companion object {
/**
- * Sets [Builder.apiKey] to an arbitrary JSON value.
- *
- * You should usually call [Builder.apiKey] with a well-typed [String] value instead. This
- * method is primarily for setting the field to an undocumented or not yet supported value.
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
*/
- fun apiKey(apiKey: JsonField) = apply { this.apiKey = apiKey }
+ @JvmStatic fun ofName(name: String) = ModelConfig(name = name)
- /** Custom base URL for API */
- fun baseUrl(baseUrl: String) = baseUrl(JsonField.of(baseUrl))
-
- /**
- * Sets [Builder.baseUrl] to an arbitrary JSON value.
- *
- * You should usually call [Builder.baseUrl] with a well-typed [String] value instead. This
- * method is primarily for setting the field to an undocumented or not yet supported value.
- */
- fun baseUrl(baseUrl: JsonField) = apply { this.baseUrl = baseUrl }
+ @JvmStatic
+ fun ofModelConfigObject(modelConfigObject: ModelConfigObject) =
+ ModelConfig(modelConfigObject = modelConfigObject)
+ }
- /** Model name */
- fun model(model: String) = model(JsonField.of(model))
+ /**
+ * An interface that defines how to map each variant of [ModelConfig] to a value of type [T].
+ */
+ interface Visitor {
/**
- * Sets [Builder.model] to an arbitrary JSON value.
- *
- * You should usually call [Builder.model] with a well-typed [String] value instead. This
- * method is primarily for setting the field to an undocumented or not yet supported value.
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
*/
- fun model(model: JsonField) = apply { this.model = model }
+ fun visitName(name: String): T
- fun provider(provider: Provider) = provider(JsonField.of(provider))
+ fun visitModelConfigObject(modelConfigObject: ModelConfigObject): T
/**
- * Sets [Builder.provider] to an arbitrary JSON value.
+ * Maps an unknown variant of [ModelConfig] to a value of type [T].
*
- * You should usually call [Builder.provider] with a well-typed [Provider] value instead.
- * This method is primarily for setting the field to an undocumented or not yet supported
- * value.
+ * An instance of [ModelConfig] can contain an unknown variant if it was deserialized from
+ * data that doesn't match any known variant. For example, if the SDK is on an older version
+ * than the API, then the API may respond with new variants that the SDK is unaware of.
+ *
+ * @throws StagehandInvalidDataException in the default implementation.
*/
- fun provider(provider: JsonField) = apply { this.provider = provider }
-
- fun additionalProperties(additionalProperties: Map) = apply {
- this.additionalProperties.clear()
- putAllAdditionalProperties(additionalProperties)
+ fun unknown(json: JsonValue?): T {
+ throw StagehandInvalidDataException("Unknown ModelConfig: $json")
}
+ }
- fun putAdditionalProperty(key: String, value: JsonValue) = apply {
- additionalProperties.put(key, value)
+ internal class Deserializer : BaseDeserializer(ModelConfig::class) {
+
+ override fun ObjectCodec.deserialize(node: JsonNode): ModelConfig {
+ val json = JsonValue.fromJsonNode(node)
+
+ val bestMatches =
+ sequenceOf(
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ ModelConfig(modelConfigObject = it, _json = json)
+ },
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ ModelConfig(name = it, _json = json)
+ },
+ )
+ .filterNotNull()
+ .allMaxBy { it.validity() }
+ .toList()
+ return when (bestMatches.size) {
+ // This can happen if what we're deserializing is completely incompatible with all
+ // the possible variants (e.g. deserializing from array).
+ 0 -> ModelConfig(_json = json)
+ 1 -> bestMatches.single()
+ // If there's more than one match with the highest validity, then use the first
+ // completely valid match, or simply the first match if none are completely valid.
+ else -> bestMatches.firstOrNull { it.isValid() } ?: bestMatches.first()
+ }
}
+ }
- fun putAllAdditionalProperties(additionalProperties: Map) = apply {
- this.additionalProperties.putAll(additionalProperties)
+ internal class Serializer : BaseSerializer(ModelConfig::class) {
+
+ override fun serialize(
+ value: ModelConfig,
+ generator: JsonGenerator,
+ provider: SerializerProvider,
+ ) {
+ when {
+ value.name != null -> generator.writeObject(value.name)
+ value.modelConfigObject != null -> generator.writeObject(value.modelConfigObject)
+ value._json != null -> generator.writeObject(value._json)
+ else -> throw IllegalStateException("Invalid ModelConfig")
+ }
}
+ }
- fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
-
- fun removeAllAdditionalProperties(keys: Set) = apply {
- keys.forEach(::removeAdditionalProperty)
- }
+ class ModelConfigObject
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val modelName: JsonField,
+ private val apiKey: JsonField,
+ private val baseUrl: JsonField,
+ private val provider: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("modelName")
+ @ExcludeMissing
+ modelName: JsonField = JsonMissing.of(),
+ @JsonProperty("apiKey") @ExcludeMissing apiKey: JsonField = JsonMissing.of(),
+ @JsonProperty("baseURL") @ExcludeMissing baseUrl: JsonField = JsonMissing.of(),
+ @JsonProperty("provider")
+ @ExcludeMissing
+ provider: JsonField = JsonMissing.of(),
+ ) : this(modelName, apiKey, baseUrl, provider, mutableMapOf())
/**
- * Returns an immutable instance of [ModelConfig].
+ * Model name string without prefix (e.g., 'gpt-5-nano', 'claude-4.5-opus')
*
- * Further updates to this [Builder] will not mutate the returned instance.
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
*/
- fun build(): ModelConfig =
- ModelConfig(apiKey, baseUrl, model, provider, additionalProperties.toMutableMap())
- }
+ fun modelName(): String = modelName.getRequired("modelName")
- private var validated: Boolean = false
+ /**
+ * API key for the model provider
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun apiKey(): Optional = apiKey.getOptional("apiKey")
- fun validate(): ModelConfig = apply {
- if (validated) {
- return@apply
- }
+ /**
+ * Base URL for the model provider
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun baseUrl(): Optional = baseUrl.getOptional("baseURL")
- apiKey()
- baseUrl()
- model()
- provider().ifPresent { it.validate() }
- validated = true
- }
+ /**
+ * AI provider for the model (or provide a baseURL endpoint instead)
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun provider(): Optional = provider.getOptional("provider")
- fun isValid(): Boolean =
- try {
- validate()
- true
- } catch (e: StagehandInvalidDataException) {
- false
- }
+ /**
+ * Returns the raw JSON value of [modelName].
+ *
+ * Unlike [modelName], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("modelName") @ExcludeMissing fun _modelName(): JsonField = modelName
- /**
- * Returns a score indicating how many valid values are contained in this object recursively.
- *
- * Used for best match union deserialization.
- */
- @JvmSynthetic
- internal fun validity(): Int =
- (if (apiKey.asKnown().isPresent) 1 else 0) +
- (if (baseUrl.asKnown().isPresent) 1 else 0) +
- (if (model.asKnown().isPresent) 1 else 0) +
- (provider.asKnown().getOrNull()?.validity() ?: 0)
+ /**
+ * Returns the raw JSON value of [apiKey].
+ *
+ * Unlike [apiKey], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("apiKey") @ExcludeMissing fun _apiKey(): JsonField = apiKey
- class Provider @JsonCreator private constructor(private val value: JsonField) : Enum {
+ /**
+ * Returns the raw JSON value of [baseUrl].
+ *
+ * Unlike [baseUrl], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("baseURL") @ExcludeMissing fun _baseUrl(): JsonField = baseUrl
/**
- * Returns this class instance's raw value.
+ * Returns the raw JSON value of [provider].
*
- * This is usually only useful if this instance was deserialized from data that doesn't
- * match any known member, and you want to know that value. For example, if the SDK is on an
- * older version than the API, then the API may respond with new members that the SDK is
- * unaware of.
+ * Unlike [provider], this method doesn't throw if the JSON field has an unexpected type.
*/
- @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
+ @JsonProperty("provider") @ExcludeMissing fun _provider(): JsonField = provider
- companion object {
+ @JsonAnySetter
+ private fun putAdditionalProperty(key: String, value: JsonValue) {
+ additionalProperties.put(key, value)
+ }
- @JvmField val OPENAI = of("openai")
+ @JsonAnyGetter
+ @ExcludeMissing
+ fun _additionalProperties(): Map =
+ Collections.unmodifiableMap(additionalProperties)
- @JvmField val ANTHROPIC = of("anthropic")
+ fun toBuilder() = Builder().from(this)
- @JvmField val GOOGLE = of("google")
+ companion object {
- @JvmStatic fun of(value: String) = Provider(JsonField.of(value))
+ /**
+ * Returns a mutable builder for constructing an instance of [ModelConfigObject].
+ *
+ * The following fields are required:
+ * ```java
+ * .modelName()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
}
- /** An enum containing [Provider]'s known values. */
- enum class Known {
- OPENAI,
- ANTHROPIC,
- GOOGLE,
- }
+ /** A builder for [ModelConfigObject]. */
+ class Builder internal constructor() {
+
+ private var modelName: JsonField? = null
+ private var apiKey: JsonField = JsonMissing.of()
+ private var baseUrl: JsonField = JsonMissing.of()
+ private var provider: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(modelConfigObject: ModelConfigObject) = apply {
+ modelName = modelConfigObject.modelName
+ apiKey = modelConfigObject.apiKey
+ baseUrl = modelConfigObject.baseUrl
+ provider = modelConfigObject.provider
+ additionalProperties = modelConfigObject.additionalProperties.toMutableMap()
+ }
- /**
- * An enum containing [Provider]'s known values, as well as an [_UNKNOWN] member.
- *
- * An instance of [Provider] can contain an unknown value in a couple of cases:
- * - It was deserialized from data that doesn't match any known member. For example, if the
- * SDK is on an older version than the API, then the API may respond with new members that
- * the SDK is unaware of.
- * - It was constructed with an arbitrary value using the [of] method.
- */
- enum class Value {
- OPENAI,
- ANTHROPIC,
- GOOGLE,
- /** An enum member indicating that [Provider] was instantiated with an unknown value. */
- _UNKNOWN,
- }
+ /** Model name string without prefix (e.g., 'gpt-5-nano', 'claude-4.5-opus') */
+ fun modelName(modelName: String) = modelName(JsonField.of(modelName))
+
+ /**
+ * Sets [Builder.modelName] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.modelName] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun modelName(modelName: JsonField) = apply { this.modelName = modelName }
+
+ /** API key for the model provider */
+ fun apiKey(apiKey: String) = apiKey(JsonField.of(apiKey))
+
+ /**
+ * Sets [Builder.apiKey] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.apiKey] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun apiKey(apiKey: JsonField) = apply { this.apiKey = apiKey }
+
+ /** Base URL for the model provider */
+ fun baseUrl(baseUrl: String) = baseUrl(JsonField.of(baseUrl))
+
+ /**
+ * Sets [Builder.baseUrl] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.baseUrl] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun baseUrl(baseUrl: JsonField) = apply { this.baseUrl = baseUrl }
+
+ /** AI provider for the model (or provide a baseURL endpoint instead) */
+ fun provider(provider: Provider) = provider(JsonField.of(provider))
+
+ /**
+ * Sets [Builder.provider] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.provider] with a well-typed [Provider] value
+ * instead. This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun provider(provider: JsonField) = apply { this.provider = provider }
+
+ fun additionalProperties(additionalProperties: Map) = apply {
+ this.additionalProperties.clear()
+ putAllAdditionalProperties(additionalProperties)
+ }
- /**
- * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN]
- * if the class was instantiated with an unknown value.
- *
- * Use the [known] method instead if you're certain the value is always known or if you want
- * to throw for the unknown case.
- */
- fun value(): Value =
- when (this) {
- OPENAI -> Value.OPENAI
- ANTHROPIC -> Value.ANTHROPIC
- GOOGLE -> Value.GOOGLE
- else -> Value._UNKNOWN
+ fun putAdditionalProperty(key: String, value: JsonValue) = apply {
+ additionalProperties.put(key, value)
}
- /**
- * Returns an enum member corresponding to this class instance's value.
- *
- * Use the [value] method instead if you're uncertain the value is always known and don't
- * want to throw for the unknown case.
- *
- * @throws StagehandInvalidDataException if this class instance's value is a not a known
- * member.
- */
- fun known(): Known =
- when (this) {
- OPENAI -> Known.OPENAI
- ANTHROPIC -> Known.ANTHROPIC
- GOOGLE -> Known.GOOGLE
- else -> throw StagehandInvalidDataException("Unknown Provider: $value")
+ fun putAllAdditionalProperties(additionalProperties: Map) = apply {
+ this.additionalProperties.putAll(additionalProperties)
}
- /**
- * Returns this class instance's primitive wire representation.
- *
- * This differs from the [toString] method because that method is primarily for debugging
- * and generally doesn't throw.
- *
- * @throws StagehandInvalidDataException if this class instance's value does not have the
- * expected primitive type.
- */
- fun asString(): String =
- _value().asString().orElseThrow {
- StagehandInvalidDataException("Value is not a String")
+ fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
+
+ fun removeAllAdditionalProperties(keys: Set) = apply {
+ keys.forEach(::removeAdditionalProperty)
}
+ /**
+ * Returns an immutable instance of [ModelConfigObject].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .modelName()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): ModelConfigObject =
+ ModelConfigObject(
+ checkRequired("modelName", modelName),
+ apiKey,
+ baseUrl,
+ provider,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
private var validated: Boolean = false
- fun validate(): Provider = apply {
+ fun validate(): ModelConfigObject = apply {
if (validated) {
return@apply
}
- known()
+ modelName()
+ apiKey()
+ baseUrl()
+ provider().ifPresent { it.validate() }
validated = true
}
@@ -353,40 +462,176 @@ private constructor(
*
* Used for best match union deserialization.
*/
- @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
+ @JvmSynthetic
+ internal fun validity(): Int =
+ (if (modelName.asKnown().isPresent) 1 else 0) +
+ (if (apiKey.asKnown().isPresent) 1 else 0) +
+ (if (baseUrl.asKnown().isPresent) 1 else 0) +
+ (provider.asKnown().getOrNull()?.validity() ?: 0)
+
+ /** AI provider for the model (or provide a baseURL endpoint instead) */
+ class Provider @JsonCreator private constructor(private val value: JsonField) :
+ Enum {
+
+ /**
+ * Returns this class instance's raw value.
+ *
+ * This is usually only useful if this instance was deserialized from data that doesn't
+ * match any known member, and you want to know that value. For example, if the SDK is
+ * on an older version than the API, then the API may respond with new members that the
+ * SDK is unaware of.
+ */
+ @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
+
+ companion object {
+
+ @JvmField val OPENAI = of("openai")
+
+ @JvmField val ANTHROPIC = of("anthropic")
+
+ @JvmField val GOOGLE = of("google")
+
+ @JvmField val MICROSOFT = of("microsoft")
+
+ @JvmStatic fun of(value: String) = Provider(JsonField.of(value))
+ }
+
+ /** An enum containing [Provider]'s known values. */
+ enum class Known {
+ OPENAI,
+ ANTHROPIC,
+ GOOGLE,
+ MICROSOFT,
+ }
+
+ /**
+ * An enum containing [Provider]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [Provider] can contain an unknown value in a couple of cases:
+ * - It was deserialized from data that doesn't match any known member. For example, if
+ * the SDK is on an older version than the API, then the API may respond with new
+ * members that the SDK is unaware of.
+ * - It was constructed with an arbitrary value using the [of] method.
+ */
+ enum class Value {
+ OPENAI,
+ ANTHROPIC,
+ GOOGLE,
+ MICROSOFT,
+ /**
+ * An enum member indicating that [Provider] was instantiated with an unknown value.
+ */
+ _UNKNOWN,
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value, or
+ * [Value._UNKNOWN] if the class was instantiated with an unknown value.
+ *
+ * Use the [known] method instead if you're certain the value is always known or if you
+ * want to throw for the unknown case.
+ */
+ fun value(): Value =
+ when (this) {
+ OPENAI -> Value.OPENAI
+ ANTHROPIC -> Value.ANTHROPIC
+ GOOGLE -> Value.GOOGLE
+ MICROSOFT -> Value.MICROSOFT
+ else -> Value._UNKNOWN
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value.
+ *
+ * Use the [value] method instead if you're uncertain the value is always known and
+ * don't want to throw for the unknown case.
+ *
+ * @throws StagehandInvalidDataException if this class instance's value is a not a known
+ * member.
+ */
+ fun known(): Known =
+ when (this) {
+ OPENAI -> Known.OPENAI
+ ANTHROPIC -> Known.ANTHROPIC
+ GOOGLE -> Known.GOOGLE
+ MICROSOFT -> Known.MICROSOFT
+ else -> throw StagehandInvalidDataException("Unknown Provider: $value")
+ }
+
+ /**
+ * Returns this class instance's primitive wire representation.
+ *
+ * This differs from the [toString] method because that method is primarily for
+ * debugging and generally doesn't throw.
+ *
+ * @throws StagehandInvalidDataException if this class instance's value does not have
+ * the expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString().orElseThrow {
+ StagehandInvalidDataException("Value is not a String")
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Provider = apply {
+ if (validated) {
+ return@apply
+ }
+
+ known()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: StagehandInvalidDataException) {
+ false
+ }
+
+ /**
+ * Returns a score indicating how many valid values are contained in this object
+ * recursively.
+ *
+ * Used for best match union deserialization.
+ */
+ @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is Provider && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
- return other is Provider && value == other.value
+ return other is ModelConfigObject &&
+ modelName == other.modelName &&
+ apiKey == other.apiKey &&
+ baseUrl == other.baseUrl &&
+ provider == other.provider &&
+ additionalProperties == other.additionalProperties
}
- override fun hashCode() = value.hashCode()
-
- override fun toString() = value.toString()
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) {
- return true
+ private val hashCode: Int by lazy {
+ Objects.hash(modelName, apiKey, baseUrl, provider, additionalProperties)
}
- return other is ModelConfig &&
- apiKey == other.apiKey &&
- baseUrl == other.baseUrl &&
- model == other.model &&
- provider == other.provider &&
- additionalProperties == other.additionalProperties
- }
+ override fun hashCode(): Int = hashCode
- private val hashCode: Int by lazy {
- Objects.hash(apiKey, baseUrl, model, provider, additionalProperties)
+ override fun toString() =
+ "ModelConfigObject{modelName=$modelName, apiKey=$apiKey, baseUrl=$baseUrl, provider=$provider, additionalProperties=$additionalProperties}"
}
-
- override fun hashCode(): Int = hashCode
-
- override fun toString() =
- "ModelConfig{apiKey=$apiKey, baseUrl=$baseUrl, model=$model, provider=$provider, additionalProperties=$additionalProperties}"
}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt
index 34cae33..a37780c 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActParams.kt
@@ -28,30 +28,43 @@ import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
+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
-/**
- * Performs a browser action based on natural language instruction or a specific action object
- * returned by observe().
- */
+/** Executes a browser action using natural language instructions or a predefined Action object. */
class SessionActParams
private constructor(
- private val sessionId: String?,
+ private val id: String?,
+ private val xLanguage: XLanguage?,
+ private val xSdkVersion: String?,
+ private val xSentAt: OffsetDateTime?,
private val xStreamResponse: XStreamResponse?,
private val body: Body,
private val additionalHeaders: Headers,
private val additionalQueryParams: QueryParams,
) : Params {
- fun sessionId(): Optional = Optional.ofNullable(sessionId)
+ /** Unique session identifier */
+ fun id(): Optional = Optional.ofNullable(id)
+ /** Client SDK language */
+ fun xLanguage(): Optional = Optional.ofNullable(xLanguage)
+
+ /** Version of the Stagehand SDK */
+ fun xSdkVersion(): Optional = Optional.ofNullable(xSdkVersion)
+
+ /** ISO timestamp when request was sent */
+ fun xSentAt(): Optional = Optional.ofNullable(xSentAt)
+
+ /** Whether to stream the response via SSE */
fun xStreamResponse(): Optional = Optional.ofNullable(xStreamResponse)
/**
- * Natural language instruction
+ * Natural language instruction or Action object
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
@@ -59,7 +72,7 @@ private constructor(
fun input(): Input = body.input()
/**
- * Frame ID to act on (optional)
+ * Target frame ID for the action
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
* server responded with an unexpected value).
@@ -119,7 +132,10 @@ private constructor(
/** A builder for [SessionActParams]. */
class Builder internal constructor() {
- private var sessionId: String? = null
+ private var id: String? = null
+ private var xLanguage: XLanguage? = null
+ private var xSdkVersion: String? = null
+ private var xSentAt: OffsetDateTime? = null
private var xStreamResponse: XStreamResponse? = null
private var body: Body.Builder = Body.builder()
private var additionalHeaders: Headers.Builder = Headers.builder()
@@ -127,18 +143,41 @@ private constructor(
@JvmSynthetic
internal fun from(sessionActParams: SessionActParams) = apply {
- sessionId = sessionActParams.sessionId
+ id = sessionActParams.id
+ xLanguage = sessionActParams.xLanguage
+ xSdkVersion = sessionActParams.xSdkVersion
+ xSentAt = sessionActParams.xSentAt
xStreamResponse = sessionActParams.xStreamResponse
body = sessionActParams.body.toBuilder()
additionalHeaders = sessionActParams.additionalHeaders.toBuilder()
additionalQueryParams = sessionActParams.additionalQueryParams.toBuilder()
}
- fun sessionId(sessionId: String?) = apply { this.sessionId = sessionId }
+ /** Unique session identifier */
+ fun id(id: String?) = apply { this.id = id }
+
+ /** Alias for calling [Builder.id] with `id.orElse(null)`. */
+ fun id(id: Optional) = id(id.getOrNull())
+
+ /** Client SDK language */
+ fun xLanguage(xLanguage: XLanguage?) = apply { this.xLanguage = xLanguage }
+
+ /** Alias for calling [Builder.xLanguage] with `xLanguage.orElse(null)`. */
+ fun xLanguage(xLanguage: Optional) = xLanguage(xLanguage.getOrNull())
+
+ /** Version of the Stagehand SDK */
+ fun xSdkVersion(xSdkVersion: String?) = apply { this.xSdkVersion = xSdkVersion }
+
+ /** Alias for calling [Builder.xSdkVersion] with `xSdkVersion.orElse(null)`. */
+ fun xSdkVersion(xSdkVersion: Optional) = xSdkVersion(xSdkVersion.getOrNull())
- /** Alias for calling [Builder.sessionId] with `sessionId.orElse(null)`. */
- fun sessionId(sessionId: Optional) = sessionId(sessionId.getOrNull())
+ /** ISO timestamp when request was sent */
+ fun xSentAt(xSentAt: OffsetDateTime?) = apply { this.xSentAt = xSentAt }
+ /** Alias for calling [Builder.xSentAt] with `xSentAt.orElse(null)`. */
+ fun xSentAt(xSentAt: Optional) = xSentAt(xSentAt.getOrNull())
+
+ /** Whether to stream the response via SSE */
fun xStreamResponse(xStreamResponse: XStreamResponse?) = apply {
this.xStreamResponse = xStreamResponse
}
@@ -158,7 +197,7 @@ private constructor(
*/
fun body(body: Body) = apply { this.body = body.toBuilder() }
- /** Natural language instruction */
+ /** Natural language instruction or Action object */
fun input(input: Input) = apply { body.input(input) }
/**
@@ -175,7 +214,7 @@ private constructor(
/** Alias for calling [input] with `Input.ofAction(action)`. */
fun input(action: Action) = apply { body.input(action) }
- /** Frame ID to act on (optional) */
+ /** Target frame ID for the action */
fun frameId(frameId: String) = apply { body.frameId(frameId) }
/**
@@ -327,7 +366,10 @@ private constructor(
*/
fun build(): SessionActParams =
SessionActParams(
- sessionId,
+ id,
+ xLanguage,
+ xSdkVersion,
+ xSentAt,
xStreamResponse,
body.build(),
additionalHeaders.build(),
@@ -339,13 +381,16 @@ private constructor(
fun _pathParam(index: Int): String =
when (index) {
- 0 -> sessionId ?: ""
+ 0 -> id ?: ""
else -> ""
}
override fun _headers(): Headers =
Headers.builder()
.apply {
+ xLanguage?.let { put("x-language", it.toString()) }
+ xSdkVersion?.let { put("x-sdk-version", it) }
+ xSentAt?.let { put("x-sent-at", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)) }
xStreamResponse?.let { put("x-stream-response", it.toString()) }
putAll(additionalHeaders)
}
@@ -370,7 +415,7 @@ private constructor(
) : this(input, frameId, options, mutableMapOf())
/**
- * Natural language instruction
+ * Natural language instruction or Action object
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
@@ -378,7 +423,7 @@ private constructor(
fun input(): Input = input.getRequired("input")
/**
- * Frame ID to act on (optional)
+ * Target frame ID for the action
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
* the server responded with an unexpected value).
@@ -453,7 +498,7 @@ private constructor(
additionalProperties = body.additionalProperties.toMutableMap()
}
- /** Natural language instruction */
+ /** Natural language instruction or Action object */
fun input(input: Input) = input(JsonField.of(input))
/**
@@ -471,7 +516,7 @@ private constructor(
/** Alias for calling [input] with `Input.ofAction(action)`. */
fun input(action: Action) = input(Input.ofAction(action))
- /** Frame ID to act on (optional) */
+ /** Target frame ID for the action */
fun frameId(frameId: String) = frameId(JsonField.of(frameId))
/**
@@ -589,7 +634,7 @@ private constructor(
"Body{input=$input, frameId=$frameId, options=$options, additionalProperties=$additionalProperties}"
}
- /** Natural language instruction */
+ /** Natural language instruction or Action object */
@JsonDeserialize(using = Input.Deserializer::class)
@JsonSerialize(using = Input.Serializer::class)
class Input
@@ -599,18 +644,18 @@ private constructor(
private val _json: JsonValue? = null,
) {
- /** Natural language instruction */
fun string(): Optional = Optional.ofNullable(string)
+ /** Action object returned by observe and used by act */
fun action(): Optional = Optional.ofNullable(action)
fun isString(): Boolean = string != null
fun isAction(): Boolean = action != null
- /** Natural language instruction */
fun asString(): String = string.getOrThrow("string")
+ /** Action object returned by observe and used by act */
fun asAction(): Action = action.getOrThrow("action")
fun _json(): Optional = Optional.ofNullable(_json)
@@ -687,18 +732,18 @@ private constructor(
companion object {
- /** Natural language instruction */
@JvmStatic fun ofString(string: String) = Input(string = string)
+ /** Action object returned by observe and used by act */
@JvmStatic fun ofAction(action: Action) = Input(action = action)
}
/** An interface that defines how to map each variant of [Input] to a value of type [T]. */
interface Visitor {
- /** Natural language instruction */
fun visitString(string: String): T
+ /** Action object returned by observe and used by act */
fun visitAction(action: Action): T
/**
@@ -767,7 +812,7 @@ private constructor(
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
private val model: JsonField,
- private val timeout: JsonField,
+ private val timeout: JsonField,
private val variables: JsonField,
private val additionalProperties: MutableMap,
) {
@@ -775,28 +820,31 @@ private constructor(
@JsonCreator
private constructor(
@JsonProperty("model") @ExcludeMissing model: JsonField = JsonMissing.of(),
- @JsonProperty("timeout") @ExcludeMissing timeout: JsonField = JsonMissing.of(),
+ @JsonProperty("timeout") @ExcludeMissing timeout: JsonField = JsonMissing.of(),
@JsonProperty("variables")
@ExcludeMissing
variables: JsonField = JsonMissing.of(),
) : this(model, timeout, variables, mutableMapOf())
/**
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
+ *
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
* the server responded with an unexpected value).
*/
fun model(): Optional = model.getOptional("model")
/**
- * Timeout in milliseconds
+ * Timeout in ms for the action
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
* the server responded with an unexpected value).
*/
- fun timeout(): Optional = timeout.getOptional("timeout")
+ fun timeout(): Optional = timeout.getOptional("timeout")
/**
- * Template variables for instruction
+ * Variables to substitute in the action instruction
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
* the server responded with an unexpected value).
@@ -815,7 +863,7 @@ private constructor(
*
* Unlike [timeout], this method doesn't throw if the JSON field has an unexpected type.
*/
- @JsonProperty("timeout") @ExcludeMissing fun _timeout(): JsonField = timeout
+ @JsonProperty("timeout") @ExcludeMissing fun _timeout(): JsonField = timeout
/**
* Returns the raw JSON value of [variables].
@@ -848,7 +896,7 @@ private constructor(
class Builder internal constructor() {
private var model: JsonField = JsonMissing.of()
- private var timeout: JsonField = JsonMissing.of()
+ private var timeout: JsonField = JsonMissing.of()
private var variables: JsonField = JsonMissing.of()
private var additionalProperties: MutableMap = mutableMapOf()
@@ -860,6 +908,10 @@ private constructor(
additionalProperties = options.additionalProperties.toMutableMap()
}
+ /**
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano',
+ * 'anthropic/claude-4.5-opus')
+ */
fun model(model: ModelConfig) = model(JsonField.of(model))
/**
@@ -871,19 +923,28 @@ private constructor(
*/
fun model(model: JsonField) = apply { this.model = model }
- /** Timeout in milliseconds */
- fun timeout(timeout: Long) = timeout(JsonField.of(timeout))
+ /** Alias for calling [model] with `ModelConfig.ofName(name)`. */
+ fun model(name: String) = model(ModelConfig.ofName(name))
+
+ /**
+ * Alias for calling [model] with `ModelConfig.ofModelConfigObject(modelConfigObject)`.
+ */
+ fun model(modelConfigObject: ModelConfig.ModelConfigObject) =
+ model(ModelConfig.ofModelConfigObject(modelConfigObject))
+
+ /** Timeout in ms for the action */
+ fun timeout(timeout: Double) = timeout(JsonField.of(timeout))
/**
* Sets [Builder.timeout] to an arbitrary JSON value.
*
- * You should usually call [Builder.timeout] with a well-typed [Long] value instead.
+ * You should usually call [Builder.timeout] with a well-typed [Double] value instead.
* This method is primarily for setting the field to an undocumented or not yet
* supported value.
*/
- fun timeout(timeout: JsonField) = apply { this.timeout = timeout }
+ fun timeout(timeout: JsonField) = apply { this.timeout = timeout }
- /** Template variables for instruction */
+ /** Variables to substitute in the action instruction */
fun variables(variables: Variables) = variables(JsonField.of(variables))
/**
@@ -956,7 +1017,7 @@ private constructor(
(if (timeout.asKnown().isPresent) 1 else 0) +
(variables.asKnown().getOrNull()?.validity() ?: 0)
- /** Template variables for instruction */
+ /** Variables to substitute in the action instruction */
class Variables
@JsonCreator
private constructor(
@@ -1081,6 +1142,143 @@ private constructor(
"Options{model=$model, timeout=$timeout, variables=$variables, additionalProperties=$additionalProperties}"
}
+ /** Client SDK language */
+ class XLanguage @JsonCreator private constructor(private val value: JsonField) : Enum {
+
+ /**
+ * Returns this class instance's raw value.
+ *
+ * This is usually only useful if this instance was deserialized from data that doesn't
+ * match any known member, and you want to know that value. For example, if the SDK is on an
+ * older version than the API, then the API may respond with new members that the SDK is
+ * unaware of.
+ */
+ @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value
+
+ companion object {
+
+ @JvmField val TYPESCRIPT = of("typescript")
+
+ @JvmField val PYTHON = of("python")
+
+ @JvmField val PLAYGROUND = of("playground")
+
+ @JvmStatic fun of(value: String) = XLanguage(JsonField.of(value))
+ }
+
+ /** An enum containing [XLanguage]'s known values. */
+ enum class Known {
+ TYPESCRIPT,
+ PYTHON,
+ PLAYGROUND,
+ }
+
+ /**
+ * An enum containing [XLanguage]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [XLanguage] can contain an unknown value in a couple of cases:
+ * - It was deserialized from data that doesn't match any known member. For example, if the
+ * SDK is on an older version than the API, then the API may respond with new members that
+ * the SDK is unaware of.
+ * - It was constructed with an arbitrary value using the [of] method.
+ */
+ enum class Value {
+ TYPESCRIPT,
+ PYTHON,
+ PLAYGROUND,
+ /**
+ * An enum member indicating that [XLanguage] was instantiated with an unknown value.
+ */
+ _UNKNOWN,
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN]
+ * if the class was instantiated with an unknown value.
+ *
+ * Use the [known] method instead if you're certain the value is always known or if you want
+ * to throw for the unknown case.
+ */
+ fun value(): Value =
+ when (this) {
+ TYPESCRIPT -> Value.TYPESCRIPT
+ PYTHON -> Value.PYTHON
+ PLAYGROUND -> Value.PLAYGROUND
+ else -> Value._UNKNOWN
+ }
+
+ /**
+ * Returns an enum member corresponding to this class instance's value.
+ *
+ * Use the [value] method instead if you're uncertain the value is always known and don't
+ * want to throw for the unknown case.
+ *
+ * @throws StagehandInvalidDataException if this class instance's value is a not a known
+ * member.
+ */
+ fun known(): Known =
+ when (this) {
+ TYPESCRIPT -> Known.TYPESCRIPT
+ PYTHON -> Known.PYTHON
+ PLAYGROUND -> Known.PLAYGROUND
+ else -> throw StagehandInvalidDataException("Unknown XLanguage: $value")
+ }
+
+ /**
+ * Returns this class instance's primitive wire representation.
+ *
+ * This differs from the [toString] method because that method is primarily for debugging
+ * and generally doesn't throw.
+ *
+ * @throws StagehandInvalidDataException if this class instance's value does not have the
+ * expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString().orElseThrow {
+ StagehandInvalidDataException("Value is not a String")
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): XLanguage = apply {
+ if (validated) {
+ return@apply
+ }
+
+ known()
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: StagehandInvalidDataException) {
+ false
+ }
+
+ /**
+ * Returns a score indicating how many valid values are contained in this object
+ * recursively.
+ *
+ * Used for best match union deserialization.
+ */
+ @JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is XLanguage && value == other.value
+ }
+
+ override fun hashCode() = value.hashCode()
+
+ override fun toString() = value.toString()
+ }
+
+ /** Whether to stream the response via SSE */
class XStreamResponse @JsonCreator private constructor(private val value: JsonField) :
Enum {
@@ -1218,7 +1416,10 @@ private constructor(
}
return other is SessionActParams &&
- sessionId == other.sessionId &&
+ id == other.id &&
+ xLanguage == other.xLanguage &&
+ xSdkVersion == other.xSdkVersion &&
+ xSentAt == other.xSentAt &&
xStreamResponse == other.xStreamResponse &&
body == other.body &&
additionalHeaders == other.additionalHeaders &&
@@ -1226,8 +1427,17 @@ private constructor(
}
override fun hashCode(): Int =
- Objects.hash(sessionId, xStreamResponse, body, additionalHeaders, additionalQueryParams)
+ Objects.hash(
+ id,
+ xLanguage,
+ xSdkVersion,
+ xSentAt,
+ xStreamResponse,
+ body,
+ additionalHeaders,
+ additionalQueryParams,
+ )
override fun toString() =
- "SessionActParams{sessionId=$sessionId, xStreamResponse=$xStreamResponse, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
+ "SessionActParams{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/models/sessions/SessionActResponse.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt
index 69dfcab..62bec51 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/models/sessions/SessionActResponse.kt
@@ -16,44 +16,31 @@ import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import java.util.Collections
import java.util.Objects
+import java.util.Optional
import kotlin.jvm.optionals.getOrNull
class SessionActResponse
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
- private val actions: JsonField>,
- private val message: JsonField,
+ private val data: JsonField,
private val success: JsonField,
private val additionalProperties: MutableMap,
) {
@JsonCreator
private constructor(
- @JsonProperty("actions")
- @ExcludeMissing
- actions: JsonField> = JsonMissing.of(),
- @JsonProperty("message") @ExcludeMissing message: JsonField = JsonMissing.of(),
+ @JsonProperty("data") @ExcludeMissing data: JsonField = JsonMissing.of(),
@JsonProperty("success") @ExcludeMissing success: JsonField = JsonMissing.of(),
- ) : this(actions, message, success, mutableMapOf())
-
- /**
- * Actions that were executed
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
- * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
- */
- fun actions(): List = actions.getRequired("actions")
+ ) : this(data, success, mutableMapOf())
/**
- * Result message
- *
* @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
*/
- fun message(): String = message.getRequired("message")
+ fun data(): Data = data.getRequired("data")
/**
- * Whether the action succeeded
+ * Indicates whether the request was successful
*
* @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
@@ -61,18 +48,11 @@ private constructor(
fun success(): Boolean = success.getRequired("success")
/**
- * Returns the raw JSON value of [actions].
- *
- * Unlike [actions], this method doesn't throw if the JSON field has an unexpected type.
- */
- @JsonProperty("actions") @ExcludeMissing fun _actions(): JsonField> = actions
-
- /**
- * Returns the raw JSON value of [message].
+ * Returns the raw JSON value of [data].
*
- * Unlike [message], this method doesn't throw if the JSON field has an unexpected type.
+ * Unlike [data], this method doesn't throw if the JSON field has an unexpected type.
*/
- @JsonProperty("message") @ExcludeMissing fun _message(): JsonField = message
+ @JsonProperty("data") @ExcludeMissing fun _data(): JsonField = data
/**
* Returns the raw JSON value of [success].
@@ -100,8 +80,7 @@ private constructor(
*
* The following fields are required:
* ```java
- * .actions()
- * .message()
+ * .data()
* .success()
* ```
*/
@@ -111,57 +90,28 @@ private constructor(
/** A builder for [SessionActResponse]. */
class Builder internal constructor() {
- private var actions: JsonField>? = null
- private var message: JsonField? = null
+ private var data: JsonField? = null
private var success: JsonField? = null
private var additionalProperties: MutableMap = mutableMapOf()
@JvmSynthetic
internal fun from(sessionActResponse: SessionActResponse) = apply {
- actions = sessionActResponse.actions.map { it.toMutableList() }
- message = sessionActResponse.message
+ data = sessionActResponse.data
success = sessionActResponse.success
additionalProperties = sessionActResponse.additionalProperties.toMutableMap()
}
- /** Actions that were executed */
- fun actions(actions: List) = actions(JsonField.of(actions))
-
- /**
- * Sets [Builder.actions] to an arbitrary JSON value.
- *
- * You should usually call [Builder.actions] with a well-typed `List` value instead.
- * This method is primarily for setting the field to an undocumented or not yet supported
- * value.
- */
- fun actions(actions: JsonField>) = apply {
- this.actions = actions.map { it.toMutableList() }
- }
-
- /**
- * Adds a single [Action] to [actions].
- *
- * @throws IllegalStateException if the field was previously set to a non-list.
- */
- fun addAction(action: Action) = apply {
- actions =
- (actions ?: JsonField.of(mutableListOf())).also {
- checkKnown("actions", it).add(action)
- }
- }
-
- /** Result message */
- fun message(message: String) = message(JsonField.of(message))
+ fun data(data: Data) = data(JsonField.of(data))
/**
- * Sets [Builder.message] to an arbitrary JSON value.
+ * Sets [Builder.data] to an arbitrary JSON value.
*
- * You should usually call [Builder.message] with a well-typed [String] value instead. This
+ * You should usually call [Builder.data] with a well-typed [Data] value instead. This
* method is primarily for setting the field to an undocumented or not yet supported value.
*/
- fun message(message: JsonField) = apply { this.message = message }
+ fun data(data: JsonField) = apply { this.data = data }
- /** Whether the action succeeded */
+ /** Indicates whether the request was successful */
fun success(success: Boolean) = success(JsonField.of(success))
/**
@@ -198,8 +148,7 @@ private constructor(
*
* The following fields are required:
* ```java
- * .actions()
- * .message()
+ * .data()
* .success()
* ```
*
@@ -207,8 +156,7 @@ private constructor(
*/
fun build(): SessionActResponse =
SessionActResponse(
- checkRequired("actions", actions).map { it.toImmutable() },
- checkRequired("message", message),
+ checkRequired("data", data),
checkRequired("success", success),
additionalProperties.toMutableMap(),
)
@@ -221,8 +169,7 @@ private constructor(
return@apply
}
- actions().forEach { it.validate() }
- message()
+ data().validate()
success()
validated = true
}
@@ -242,9 +189,867 @@ private constructor(
*/
@JvmSynthetic
internal fun validity(): Int =
- (actions.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
- (if (message.asKnown().isPresent) 1 else 0) +
- (if (success.asKnown().isPresent) 1 else 0)
+ (data.asKnown().getOrNull()?.validity() ?: 0) + (if (success.asKnown().isPresent) 1 else 0)
+
+ class Data
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val result: JsonField,
+ private val actionId: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("result") @ExcludeMissing result: JsonField = JsonMissing.of(),
+ @JsonProperty("actionId") @ExcludeMissing actionId: JsonField = JsonMissing.of(),
+ ) : this(result, actionId, mutableMapOf())
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected value).
+ */
+ fun result(): Result = result.getRequired("result")
+
+ /**
+ * Action ID for tracking
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun actionId(): Optional = actionId.getOptional("actionId")
+
+ /**
+ * Returns the raw JSON value of [result].
+ *
+ * Unlike [result], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("result") @ExcludeMissing fun _result(): JsonField = result
+
+ /**
+ * Returns the raw JSON value of [actionId].
+ *
+ * Unlike [actionId], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("actionId") @ExcludeMissing fun _actionId(): JsonField = actionId
+
+ @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 [Data].
+ *
+ * The following fields are required:
+ * ```java
+ * .result()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Data]. */
+ class Builder internal constructor() {
+
+ private var result: JsonField? = null
+ private var actionId: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(data: Data) = apply {
+ result = data.result
+ actionId = data.actionId
+ additionalProperties = data.additionalProperties.toMutableMap()
+ }
+
+ fun result(result: Result) = result(JsonField.of(result))
+
+ /**
+ * Sets [Builder.result] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.result] with a well-typed [Result] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun result(result: JsonField) = apply { this.result = result }
+
+ /** Action ID for tracking */
+ fun actionId(actionId: String) = actionId(JsonField.of(actionId))
+
+ /**
+ * Sets [Builder.actionId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.actionId] with a well-typed [String] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun actionId(actionId: JsonField) = apply { this.actionId = actionId }
+
+ 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 [Data].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .result()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Data =
+ Data(checkRequired("result", result), actionId, additionalProperties.toMutableMap())
+ }
+
+ private var validated: Boolean = false
+
+ fun validate(): Data = apply {
+ if (validated) {
+ return@apply
+ }
+
+ result().validate()
+ actionId()
+ 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 =
+ (result.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (actionId.asKnown().isPresent) 1 else 0)
+
+ class Result
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val actionDescription: JsonField,
+ private val actions: JsonField>,
+ private val message: JsonField,
+ private val success: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("actionDescription")
+ @ExcludeMissing
+ actionDescription: JsonField = JsonMissing.of(),
+ @JsonProperty("actions")
+ @ExcludeMissing
+ actions: JsonField> = JsonMissing.of(),
+ @JsonProperty("message")
+ @ExcludeMissing
+ message: JsonField = JsonMissing.of(),
+ @JsonProperty("success")
+ @ExcludeMissing
+ success: JsonField = JsonMissing.of(),
+ ) : this(actionDescription, actions, message, success, mutableMapOf())
+
+ /**
+ * Description of the action that was performed
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected
+ * value).
+ */
+ fun actionDescription(): String = actionDescription.getRequired("actionDescription")
+
+ /**
+ * List of actions that were executed
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected
+ * value).
+ */
+ fun actions(): List = actions.getRequired("actions")
+
+ /**
+ * Human-readable result message
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected
+ * value).
+ */
+ fun message(): String = message.getRequired("message")
+
+ /**
+ * Whether the action completed successfully
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type or is
+ * unexpectedly missing or null (e.g. if the server responded with an unexpected
+ * value).
+ */
+ fun success(): Boolean = success.getRequired("success")
+
+ /**
+ * Returns the raw JSON value of [actionDescription].
+ *
+ * Unlike [actionDescription], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("actionDescription")
+ @ExcludeMissing
+ fun _actionDescription(): JsonField = actionDescription
+
+ /**
+ * Returns the raw JSON value of [actions].
+ *
+ * Unlike [actions], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("actions")
+ @ExcludeMissing
+ fun _actions(): JsonField> = actions
+
+ /**
+ * Returns the raw JSON value of [message].
+ *
+ * Unlike [message], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("message") @ExcludeMissing fun _message(): JsonField = message
+
+ /**
+ * Returns the raw JSON value of [success].
+ *
+ * Unlike [success], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("success") @ExcludeMissing fun _success(): JsonField = success
+
+ @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 [Result].
+ *
+ * The following fields are required:
+ * ```java
+ * .actionDescription()
+ * .actions()
+ * .message()
+ * .success()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Result]. */
+ class Builder internal constructor() {
+
+ private var actionDescription: JsonField