diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a8d4c5a..b11acac 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,10 +22,10 @@ jobs:
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -34,7 +34,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/actions/setup-gradle@v4
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Run lints
run: ./scripts/lint
@@ -49,10 +49,10 @@ jobs:
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -61,7 +61,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/actions/setup-gradle@v4
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Build SDK
run: ./scripts/build
@@ -71,7 +71,7 @@ jobs:
github.repository == 'stainless-sdks/stagehand-java' &&
!startsWith(github.ref, 'refs/heads/stl/')
id: github-oidc
- uses: actions/github-script@v8
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: core.setOutput('github_token', await core.getIDToken());
@@ -91,10 +91,10 @@ jobs:
runs-on: ${{ github.repository == 'stainless-sdks/stagehand-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -103,7 +103,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@v2
+ uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Run tests
run: ./scripts/test
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index e0e380c..ebab6f0 100644
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
- uses: actions/setup-java@v5
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
@@ -26,7 +26,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@v2
+ uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Publish to Sonatype
run: |-
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index e71595c..4fcd85e 100644
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -12,7 +12,7 @@ jobs:
if: github.repository == 'browserbase/stagehand-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check release environment
run: |
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index d11c8fc..eba8a04 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "3.20.0"
+ ".": "3.21.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 391cde3..15099ca 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 8
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-6f6bfb81d092f30a5e2005328c97d61b9ea36132bb19e9e79e55294b9534ce20.yml
-openapi_spec_hash: f3fc1e3688a38dc2c28f7178f7d534e5
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase/stagehand-c7910965e66e73ad8b65b6cc391d431094b2a6c6577c3e9d82feaa8138e74cff.yml
+openapi_spec_hash: 37748bb69c22a9ce721d9b5a5861f964
config_hash: 1fb12ae9b478488bc1e56bfbdc210b01
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d94df07..1677dfa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,27 @@
# Changelog
+## 3.21.0 (2026-05-29)
+
+Full Changelog: [v3.20.0...v3.21.0](https://github.com/browserbase/stagehand-java/compare/v3.20.0...v3.21.0)
+
+### Features
+
+* [feat]: add `ignoreSelectors` to `observe()` ([aa7ad0c](https://github.com/browserbase/stagehand-java/commit/aa7ad0c2033478107f3944b625d1b172b8f39e83))
+* [STG-1756] forward Vertex model config ([b37b827](https://github.com/browserbase/stagehand-java/commit/b37b827f38cc168d12e0d3a355f25a137796f5d7))
+* Add `screenshot` option to Extract ([043d138](https://github.com/browserbase/stagehand-java/commit/043d138a98a8caab6bc2c59c2abb0a864019c3c5))
+* **client:** improve logging ([1d84566](https://github.com/browserbase/stagehand-java/commit/1d845661917baf3d12a97286cf760e0d0fda0218))
+* STG-1756 add Vertex auth params to Stagehand spec ([f072067](https://github.com/browserbase/stagehand-java/commit/f072067e7adef04886423e1232e6b7e42e5f8a28))
+
+
+### Bug Fixes
+
+* **java:** update model config examples ([991a894](https://github.com/browserbase/stagehand-java/commit/991a894f6c93371eaf18a5878a41a4e27928adc8))
+
+
+### Chores
+
+* redact api-key headers in debug logs ([ce7e1d2](https://github.com/browserbase/stagehand-java/commit/ce7e1d278d5f7a9ceda6bc3108ed8d097f9b313d))
+
## 3.20.0 (2026-05-06)
Full Changelog: [v3.19.3...v3.20.0](https://github.com/browserbase/stagehand-java/compare/v3.19.3...v3.20.0)
diff --git a/README.md b/README.md
index 86c046b..b18062f 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
-[](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/3.20.0)
-[](https://javadoc.io/doc/com.browserbase.api/stagehand-java/3.20.0)
+[](https://central.sonatype.com/artifact/com.browserbase.api/stagehand-java/3.21.0)
+[](https://javadoc.io/doc/com.browserbase.api/stagehand-java/3.21.0)
@@ -85,7 +85,7 @@ Most existing browser automation tools either require you to write low-level cod
### Gradle
```kotlin
-implementation("com.browserbase.api:stagehand-java:3.20.0")
+implementation("com.browserbase.api:stagehand-java:3.21.0")
```
### Maven
@@ -94,7 +94,7 @@ implementation("com.browserbase.api:stagehand-java:3.20.0")
com.browserbase.api
stagehand-java
- 3.20.0
+ 3.21.0
```
@@ -526,8 +526,6 @@ The SDK throws custom unchecked exception types:
## Logging
-The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
-
Enable logging by setting the `STAGEHAND_LOG` environment variable to `info`:
```sh
@@ -540,6 +538,19 @@ Or to `debug` for more verbose logging:
export STAGEHAND_LOG=debug
```
+Or configure the client manually using the `logLevel` method:
+
+```java
+import com.browserbase.api.client.StagehandClient;
+import com.browserbase.api.client.okhttp.StagehandOkHttpClient;
+import com.browserbase.api.core.LogLevel;
+
+StagehandClient client = StagehandOkHttpClient.builder()
+ .fromEnv()
+ .logLevel(LogLevel.INFO)
+ .build();
+```
+
## ProGuard and R8
Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `stagehand-java-core` is published with a [configuration file](stagehand-java-core/src/main/resources/META-INF/proguard/stagehand-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).
diff --git a/build.gradle.kts b/build.gradle.kts
index 4602d16..2f55137 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.browserbase.api"
- version = "3.20.0" // x-release-please-version
+ version = "3.21.0" // x-release-please-version
}
subprojects {
diff --git a/stagehand-java-client-okhttp/build.gradle.kts b/stagehand-java-client-okhttp/build.gradle.kts
index 49e7e71..be765aa 100644
--- a/stagehand-java-client-okhttp/build.gradle.kts
+++ b/stagehand-java-client-okhttp/build.gradle.kts
@@ -7,7 +7,6 @@ dependencies {
api(project(":stagehand-java-core"))
implementation("com.squareup.okhttp3:okhttp:4.12.0")
- implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
testImplementation(kotlin("test"))
testImplementation("org.assertj:assertj-core:3.27.7")
diff --git a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt
index 2d3b42f..155ae2b 100644
--- a/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt
+++ b/stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/OkHttpClient.kt
@@ -35,7 +35,6 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
-import okhttp3.logging.HttpLoggingInterceptor
import okio.BufferedSink
import okio.buffer
import okio.sink
@@ -93,21 +92,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
val clientBuilder = okHttpClient.newBuilder()
- val logLevel =
- when (System.getenv("STAGEHAND_LOG")?.lowercase()) {
- "info" -> HttpLoggingInterceptor.Level.BASIC
- "debug" -> HttpLoggingInterceptor.Level.BODY
- else -> null
- }
- if (logLevel != null) {
- clientBuilder.addNetworkInterceptor(
- HttpLoggingInterceptor().setLevel(logLevel).apply {
- redactHeader("x-bb-api-key")
- redactHeader("x-model-api-key")
- }
- )
- }
-
requestOptions.timeout?.let {
clientBuilder
.connectTimeout(it.connect())
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 972d3e3..36e0fe3 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
@@ -5,6 +5,7 @@ package com.browserbase.api.client.okhttp
import com.browserbase.api.client.StagehandClient
import com.browserbase.api.client.StagehandClientImpl
import com.browserbase.api.core.ClientOptions
+import com.browserbase.api.core.LogLevel
import com.browserbase.api.core.Sleeper
import com.browserbase.api.core.Timeout
import com.browserbase.api.core.http.AsyncStreamResponse
@@ -290,6 +291,15 @@ class StagehandOkHttpClient private constructor() {
*/
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
+
/** Your [Browserbase API Key](https://www.browserbase.com/settings) */
fun browserbaseApiKey(browserbaseApiKey: String) = apply {
clientOptions.browserbaseApiKey(browserbaseApiKey)
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 321443a..83bb57e 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
@@ -5,6 +5,7 @@ package com.browserbase.api.client.okhttp
import com.browserbase.api.client.StagehandClientAsync
import com.browserbase.api.client.StagehandClientAsyncImpl
import com.browserbase.api.core.ClientOptions
+import com.browserbase.api.core.LogLevel
import com.browserbase.api.core.Sleeper
import com.browserbase.api.core.Timeout
import com.browserbase.api.core.http.AsyncStreamResponse
@@ -290,6 +291,15 @@ class StagehandOkHttpClientAsync private constructor() {
*/
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
+
/** Your [Browserbase API Key](https://www.browserbase.com/settings) */
fun browserbaseApiKey(browserbaseApiKey: String) = apply {
clientOptions.browserbaseApiKey(browserbaseApiKey)
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 90fa08b..1f88c0a 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
@@ -5,6 +5,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.LoggingHttpClient
import com.browserbase.api.core.http.PhantomReachableClosingHttpClient
import com.browserbase.api.core.http.QueryParams
import com.browserbase.api.core.http.RetryingHttpClient
@@ -110,6 +111,14 @@ private constructor(
* Defaults to 2.
*/
@get:JvmName("maxRetries") val maxRetries: Int,
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ @get:JvmName("logLevel") val logLevel: LogLevel,
/** Your [Browserbase API Key](https://www.browserbase.com/settings) */
@get:JvmName("browserbaseApiKey") val browserbaseApiKey: String,
private val browserbaseProjectId: String?,
@@ -176,6 +185,7 @@ private constructor(
private var responseValidation: Boolean = false
private var timeout: Timeout = Timeout.default()
private var maxRetries: Int = 2
+ private var logLevel: LogLevel = LogLevel.fromEnv()
private var browserbaseApiKey: String? = null
private var browserbaseProjectId: String? = null
private var modelApiKey: String? = null
@@ -194,6 +204,7 @@ private constructor(
responseValidation = clientOptions.responseValidation
timeout = clientOptions.timeout
maxRetries = clientOptions.maxRetries
+ logLevel = clientOptions.logLevel
browserbaseApiKey = clientOptions.browserbaseApiKey
browserbaseProjectId = clientOptions.browserbaseProjectId
modelApiKey = clientOptions.modelApiKey
@@ -320,6 +331,15 @@ private constructor(
*/
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
+ /**
+ * The level at which to log request and response information.
+ *
+ * [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
+ *
+ * Defaults to [LogLevel.fromEnv].
+ */
+ fun logLevel(logLevel: LogLevel) = apply { this.logLevel = logLevel }
+
/** Your [Browserbase API Key](https://www.browserbase.com/settings) */
fun browserbaseApiKey(browserbaseApiKey: String) = apply {
this.browserbaseApiKey = browserbaseApiKey
@@ -327,7 +347,7 @@ private constructor(
/**
* Deprecated. Browserbase API keys are now project-scoped, so this value is no longer
- * required. Accepted for backwards compatibility; it is ignored.
+ * required.
*/
fun browserbaseProjectId(browserbaseProjectId: String?) = apply {
this.browserbaseProjectId = browserbaseProjectId
@@ -430,23 +450,28 @@ private constructor(
*
* See this table for the available options:
*
- * |Setter |System property |Environment variable |Required|Default value |
- * |-------------------|-----------------------------|---------------------|--------|-----------------------------------------|
- * |`browserbaseApiKey`|`stagehand.browserbaseApiKey`|`BROWSERBASE_API_KEY`|true |- |
- * |`modelApiKey` |`stagehand.modelApiKey` |`MODEL_API_KEY` |true |- |
- * |`baseUrl` |`stagehand.baseUrl` |`STAGEHAND_API_URL` |false |`"https://api.stagehand.browserbase.com"`|
+ * |Setter |System property |Environment variable |Required|Default value |
+ * |----------------------|--------------------------------|------------------------|--------|-----------------------------------------|
+ * |`browserbaseApiKey` |`stagehand.browserbaseApiKey` |`BROWSERBASE_API_KEY` |true |- |
+ * |`browserbaseProjectId`|`stagehand.browserbaseProjectId`|`BROWSERBASE_PROJECT_ID`|false |- |
+ * |`modelApiKey` |`stagehand.modelApiKey` |`MODEL_API_KEY` |true |- |
+ * |`baseUrl` |`stagehand.baseUrl` |`STAGEHAND_BASE_URL` |true |`"https://api.stagehand.browserbase.com"`|
*
* System properties take precedence over environment variables.
*/
fun fromEnv() = fromEnv(System::getenv)
- internal fun fromEnv(getEnv: (String) -> String?) = apply {
+ fun fromEnv(getEnv: (String) -> String?) = apply {
+ logLevel(LogLevel.fromEnv())
(System.getProperty("stagehand.baseUrl")
?: getEnv("STAGEHAND_API_URL")
?: getEnv("STAGEHAND_BASE_URL"))
?.let { baseUrl(it) }
(System.getProperty("stagehand.browserbaseApiKey") ?: getEnv("BROWSERBASE_API_KEY"))
?.let { browserbaseApiKey(it) }
+ (System.getProperty("stagehand.browserbaseProjectId")
+ ?: getEnv("BROWSERBASE_PROJECT_ID"))
+ ?.let { browserbaseProjectId(it) }
(System.getProperty("stagehand.modelApiKey") ?: getEnv("MODEL_API_KEY"))?.let {
modelApiKey(it)
}
@@ -525,7 +550,13 @@ private constructor(
return ClientOptions(
httpClient,
RetryingHttpClient.builder()
- .httpClient(httpClient)
+ .httpClient(
+ LoggingHttpClient.builder()
+ .httpClient(httpClient)
+ .clock(clock)
+ .level(logLevel)
+ .build()
+ )
.sleeper(sleeper)
.clock(clock)
.maxRetries(maxRetries)
@@ -541,6 +572,7 @@ private constructor(
responseValidation,
timeout,
maxRetries,
+ logLevel,
browserbaseApiKey,
browserbaseProjectId,
modelApiKey,
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/LogLevel.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/LogLevel.kt
new file mode 100644
index 0000000..f5a07a1
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/LogLevel.kt
@@ -0,0 +1,33 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.browserbase.api.core
+
+/** The level at which to log request and response information. */
+enum class LogLevel {
+ /** No logging. */
+ OFF,
+ /** Minimal request and response summary logs. No headers or bodies are logged. */
+ INFO,
+ /** [INFO] logs plus details about request failures. */
+ ERROR,
+ /**
+ * Full request and response logs. Sensitive headers are redacted, but sensitive data in request
+ * and response bodies may still be visible.
+ */
+ DEBUG;
+
+ /** Returns whether this level is at or higher than the given [level]. */
+ fun shouldLog(level: LogLevel): Boolean = ordinal >= level.ordinal
+
+ companion object {
+
+ /** Returns a [LogLevel] based on the `STAGEHAND_LOG` environment variable. */
+ fun fromEnv() =
+ when (System.getenv("STAGEHAND_LOG")?.lowercase()) {
+ "info" -> INFO
+ "error" -> ERROR
+ "debug" -> DEBUG
+ else -> OFF
+ }
+ }
+}
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/Utils.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/Utils.kt
index c24f648..ee3da60 100644
--- a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/Utils.kt
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/Utils.kt
@@ -5,6 +5,7 @@ package com.browserbase.api.core
import com.browserbase.api.errors.StagehandInvalidDataException
import java.util.Collections
import java.util.SortedMap
+import java.util.SortedSet
import java.util.concurrent.CompletableFuture
import java.util.concurrent.locks.Lock
@@ -16,6 +17,11 @@ internal fun T?.getOrThrow(name: String): T =
internal fun List.toImmutable(): List =
if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList())
+@JvmSynthetic
+internal fun > SortedSet.toImmutable(): SortedSet =
+ if (isEmpty()) Collections.emptySortedSet()
+ else Collections.unmodifiableSortedSet(toSortedSet(comparator() ?: Comparator.naturalOrder()))
+
@JvmSynthetic
internal fun Map.toImmutable(): Map =
if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap())
diff --git a/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/LoggingHttpClient.kt b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/LoggingHttpClient.kt
new file mode 100644
index 0000000..564bbfc
--- /dev/null
+++ b/stagehand-java-core/src/main/kotlin/com/browserbase/api/core/http/LoggingHttpClient.kt
@@ -0,0 +1,639 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.browserbase.api.core.http
+
+import com.browserbase.api.core.LogLevel
+import com.browserbase.api.core.RequestOptions
+import com.browserbase.api.core.checkRequired
+import com.browserbase.api.core.toImmutable
+import java.io.ByteArrayOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.nio.ByteBuffer
+import java.nio.charset.CharacterCodingException
+import java.nio.charset.Charset
+import java.nio.charset.CharsetDecoder
+import java.nio.charset.CodingErrorAction
+import java.nio.charset.StandardCharsets
+import java.time.Clock
+import java.time.Duration
+import java.time.OffsetDateTime
+import java.util.SortedSet
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionException
+import kotlin.time.toKotlinDuration
+
+/** A wrapper [HttpClient] around [httpClient] that logs request and response information. */
+class LoggingHttpClient
+private constructor(
+ /** The underlying [HttpClient] for making requests. */
+ @get:JvmName("httpClient") val httpClient: HttpClient,
+ /**
+ * Sensitive headers to redact from logs.
+ *
+ * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie",
+ * "x-bb-api-key", "x-bb-project-id", "x-model-api-key")`.
+ */
+ @get:JvmName("redactedHeaders") val redactedHeaders: SortedSet,
+ /**
+ * The clock to use for measuring request and response durations.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
+ @get:JvmName("clock") val clock: Clock,
+ /**
+ * The log level to use.
+ *
+ * Pass [LogLevel.fromEnv] to read from environment variables.
+ */
+ @get:JvmName("level") val level: LogLevel,
+) : HttpClient {
+
+ override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
+ val loggingRequest = logRequest(request)
+
+ val before = OffsetDateTime.now(clock)
+ val response =
+ try {
+ httpClient.execute(loggingRequest, requestOptions)
+ } catch (e: Throwable) {
+ logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
+ throw e
+ }
+
+ val took = Duration.between(before, OffsetDateTime.now(clock))
+ return logResponse(response, took)
+ }
+
+ override fun executeAsync(
+ request: HttpRequest,
+ requestOptions: RequestOptions,
+ ): CompletableFuture {
+ val loggingRequest = logRequest(request)
+
+ val before = OffsetDateTime.now(clock)
+ val future =
+ try {
+ httpClient.executeAsync(loggingRequest, requestOptions)
+ } catch (e: Throwable) {
+ logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
+ throw e
+ }
+ return future.handle { response, error ->
+ val took = Duration.between(before, OffsetDateTime.now(clock))
+ if (error != null) {
+ logFailure(unwrapCompletionException(error), took)
+ throw error
+ }
+ logResponse(response, took)
+ }
+ }
+
+ private fun logRequest(request: HttpRequest): HttpRequest {
+ if (!level.shouldLog(LogLevel.INFO)) {
+ return request
+ }
+
+ System.err.println(
+ buildString {
+ append("--> ${request.method} ${request.url()}")
+ request.body?.let {
+ val length = it.contentLength()
+ append(if (length >= 0) " ($length-byte body)" else " (unknown-length body)")
+ }
+ }
+ )
+
+ if (!level.shouldLog(LogLevel.DEBUG)) {
+ return request
+ }
+
+ logHeaders(request.headers)
+
+ if (request.body == null) {
+ System.err.println("--> END ${request.method}")
+ System.err.println()
+ return request
+ }
+
+ return request
+ .toBuilder()
+ .body(LoggingHttpRequestBody(request.method, request.body))
+ .build()
+ }
+
+ private fun logResponse(response: HttpResponse, took: Duration): HttpResponse {
+ if (!level.shouldLog(LogLevel.INFO)) {
+ return response
+ }
+
+ val contentLength = response.headers().values("Content-Length").firstOrNull()?.toIntOrNull()
+ System.err.println(
+ "<-- ${response.statusCode()} (${
+ buildString {
+ append(took.format())
+ contentLength?.let { append(", $contentLength-byte body") }
+ }
+ })"
+ )
+
+ if (!level.shouldLog(LogLevel.DEBUG)) {
+ return response
+ }
+
+ logHeaders(response.headers())
+ return LoggingHttpResponse(response)
+ }
+
+ private fun logFailure(error: Throwable, took: Duration) {
+ if (!level.shouldLog(LogLevel.ERROR)) {
+ return
+ }
+
+ System.err.println(
+ buildString {
+ append("<-- !! ${error.javaClass.simpleName}")
+ error.message?.let { append(": $it") }
+ append(" (${took.format()})")
+ }
+ )
+ }
+
+ private fun unwrapCompletionException(error: Throwable): Throwable =
+ if (error is CompletionException && error.cause != null) error.cause!! else error
+
+ private fun logHeaders(headers: Headers) =
+ headers.names().forEach { name ->
+ headers.values(name).forEach { value ->
+ System.err.println("$name: ${if (redactedHeaders.contains(name)) "██" else value}")
+ }
+ }
+
+ override fun close() = httpClient.close()
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /**
+ * Returns a mutable builder for constructing an instance of [LoggingHttpClient].
+ *
+ * The following fields are required:
+ * ```java
+ * .httpClient()
+ * .level()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [LoggingHttpClient]. */
+ class Builder internal constructor() {
+
+ private var httpClient: HttpClient? = null
+ private var redactedHeaders: Set =
+ setOf(
+ "authorization",
+ "api-key",
+ "x-api-key",
+ "cookie",
+ "set-cookie",
+ "x-bb-api-key",
+ "x-bb-project-id",
+ "x-model-api-key",
+ )
+ private var clock: Clock = Clock.systemUTC()
+ private var level: LogLevel? = null
+
+ @JvmSynthetic
+ internal fun from(loggingHttpClient: LoggingHttpClient) = apply {
+ httpClient = loggingHttpClient.httpClient
+ redactedHeaders = loggingHttpClient.redactedHeaders
+ clock = loggingHttpClient.clock
+ level = loggingHttpClient.level
+ }
+
+ /** The underlying [HttpClient] for making requests. */
+ fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
+
+ /**
+ * Sensitive headers to redact from logs.
+ *
+ * Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie",
+ * "x-bb-api-key", "x-bb-project-id", "x-model-api-key")`.
+ */
+ fun redactedHeaders(redactedHeaders: Set) = apply {
+ this.redactedHeaders = redactedHeaders
+ }
+
+ /**
+ * The clock to use for measuring request and response durations.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
+ fun clock(clock: Clock) = apply { this.clock = clock }
+
+ /**
+ * The log level to use.
+ *
+ * Pass [LogLevel.fromEnv] to read from environment variables.
+ */
+ fun level(level: LogLevel) = apply { this.level = level }
+
+ /**
+ * Returns an immutable instance of [LoggingHttpClient].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .httpClient()
+ * .level()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): LoggingHttpClient =
+ LoggingHttpClient(
+ checkRequired("httpClient", httpClient),
+ redactedHeaders.toSortedSet(String.CASE_INSENSITIVE_ORDER).toImmutable(),
+ clock,
+ checkRequired("level", level),
+ )
+ }
+}
+
+/**
+ * An [HttpRequestBody] wrapper that delegates to [body] while also logging line by line as it's
+ * written.
+ *
+ * The logging occurs in a streaming manner with minimal buffering.
+ */
+private class LoggingHttpRequestBody(
+ private val method: HttpMethod,
+ private val body: HttpRequestBody,
+) : HttpRequestBody {
+
+ private val charset by lazy { parseCharset(body.contentType()) }
+
+ override fun writeTo(outputStream: OutputStream) {
+ val loggingOutputStream = LoggingOutputStream(outputStream, charset)
+ body.writeTo(loggingOutputStream)
+
+ loggingOutputStream.flush()
+ System.err.println("--> END $method (${loggingOutputStream.writeCount()}-byte body)")
+ System.err.println()
+ }
+
+ override fun contentType(): String? = body.contentType()
+
+ override fun contentLength(): Long = body.contentLength()
+
+ override fun repeatable(): Boolean = body.repeatable()
+
+ override fun close() = body.close()
+}
+
+/**
+ * An [OutputStream] wrapper that delegates to [outputStream] while also logging bytes line by line
+ * as it's written to.
+ *
+ * The written content is assumed to be in the given [charset] and the logging occurs in a streaming
+ * manner with minimal buffering.
+ */
+private class LoggingOutputStream(private val outputStream: OutputStream, charset: Charset?) :
+ OutputStream() {
+
+ private val buffer = LoggingBuffer(charset)
+
+ fun writeCount() = buffer.writeCount()
+
+ override fun write(b: Int) {
+ outputStream.write(b)
+ buffer.write(b)
+ }
+
+ override fun write(b: ByteArray, off: Int, len: Int) {
+ outputStream.write(b, off, len)
+ for (i in off until off + len) {
+ buffer.write(b[i].toInt() and 0xFF)
+ }
+ }
+
+ /** Prints any currently buffered content. */
+ override fun flush() {
+ buffer.flush()
+ outputStream.flush()
+ }
+
+ override fun close() = outputStream.close()
+}
+
+/**
+ * An [HttpResponse] wrapper that delegates to [response] while also logging line-by-line as it's
+ * read.
+ *
+ * The logging occurs in a streaming manner with minimal buffering.
+ */
+private class LoggingHttpResponse(private val response: HttpResponse) : HttpResponse {
+
+ private val loggingBody: Lazy = lazy {
+ LoggingInputStream(
+ response.body(),
+ parseCharset(response.headers().values("Content-Type").firstOrNull()),
+ )
+ }
+
+ override fun statusCode(): Int = response.statusCode()
+
+ override fun headers(): Headers = response.headers()
+
+ override fun body(): InputStream = loggingBody.value
+
+ override fun close() {
+ if (loggingBody.isInitialized()) {
+ loggingBody.value.close()
+ }
+ response.close()
+ }
+}
+
+/**
+ * An [InputStream] wrapper that delegates to [inputStream] while also logging bytes line by line as
+ * it's read.
+ *
+ * The contents of [inputStream] are assumed to be in the given [charset] and the logging occurs in
+ * a streaming manner with minimal buffering.
+ */
+private class LoggingInputStream(private val inputStream: InputStream, charset: Charset?) :
+ InputStream() {
+
+ private var isDone = false
+ private val buffer = LoggingBuffer(charset)
+
+ override fun read(): Int {
+ if (isDone) {
+ return -1
+ }
+
+ val b = inputStream.read()
+
+ if (b == -1) {
+ markDone()
+ return b
+ }
+
+ buffer.write(b)
+ return b
+ }
+
+ override fun read(b: ByteArray, off: Int, len: Int): Int {
+ if (isDone) {
+ return -1
+ }
+
+ val bytesRead = inputStream.read(b, off, len)
+
+ if (bytesRead == -1) {
+ markDone()
+ return bytesRead
+ }
+
+ for (i in off until off + bytesRead) {
+ buffer.write(b[i].toInt() and 0xFF)
+ }
+ return bytesRead
+ }
+
+ override fun close() {
+ if (!isDone) {
+ markDone(closedEarly = true)
+ }
+ inputStream.close()
+ }
+
+ private fun markDone(closedEarly: Boolean = false) {
+ isDone = true
+ buffer.flush()
+ val suffix = if (closedEarly) ", closed early" else ""
+ System.err.println("<-- END HTTP (${buffer.writeCount()}-byte body$suffix)")
+ System.err.println()
+ }
+}
+
+/**
+ * A byte buffer that prints line by line, using the given [charset], as bytes are written to it.
+ *
+ * When [charset] is `null`, the buffer performs an upfront check to detect binary content. If
+ * non-whitespace ISO control characters are found in the first [PROBABLY_UTF8_CODE_POINT_LIMIT]
+ * code points, body logging is suppressed entirely.
+ */
+private class LoggingBuffer(charset: Charset?) {
+
+ private val charset = charset ?: StandardCharsets.UTF_8
+
+ private val decoder: CharsetDecoder =
+ this.charset
+ .newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ private var writeCount = 0
+ private val buffer = ByteArrayOutputStream(128)
+
+ /**
+ * Whether logging has been suppressed because the content doesn't appear to be readable text.
+ *
+ * This is only set when [charset] is `null` and the content fails the [isProbablyUtf8] check.
+ */
+ private var suppressed = false
+
+ /**
+ * Bytes accumulated for the [isProbablyUtf8] check before any lines are printed.
+ *
+ * Once the check passes (or [charset] is non-null), this is set to `null` and bytes flow
+ * directly to [buffer].
+ */
+ private var prefetchBuffer: ByteArrayOutputStream? =
+ if (charset != null) null else ByteArrayOutputStream(128)
+
+ fun writeCount() = writeCount
+
+ fun write(b: Int) {
+ if (writeCount == 0) {
+ // Print a newline before we start printing anything to separate the printed content
+ // from previous content.
+ System.err.println()
+ }
+
+ writeCount++
+
+ if (suppressed) {
+ return
+ }
+
+ val prefetch = prefetchBuffer
+ if (prefetch != null) {
+ prefetch.write(b)
+ // Continue accumulating until we have enough bytes to decide.
+ if (prefetch.size() < PROBABLY_UTF8_BYTE_LIMIT && b != '\n'.code) {
+ return
+ }
+ // We have enough bytes. Check if the content is probably UTF-8.
+ prefetchBuffer = null
+ val bytes = prefetch.toByteArray()
+ if (!isProbablyUtf8(bytes)) {
+ suppressed = true
+ System.err.println("(binary body omitted)")
+ return
+ }
+ // Content looks like UTF-8. Feed the accumulated bytes into the normal buffer.
+ for (byte in bytes) {
+ writeToBuffer(byte.toInt() and 0xFF)
+ }
+ return
+ }
+
+ writeToBuffer(b)
+ }
+
+ private fun writeToBuffer(b: Int) {
+ if (b == '\n'.code) {
+ flush()
+ return
+ }
+
+ buffer.write(b)
+ }
+
+ /** Prints any currently buffered content. */
+ fun flush() {
+ if (suppressed) {
+ return
+ }
+
+ // If we still have a prefetch buffer when flush is called (body was shorter than the
+ // limit), run the check now.
+ val prefetch = prefetchBuffer
+ if (prefetch != null) {
+ prefetchBuffer = null
+ val bytes = prefetch.toByteArray()
+ if (bytes.isEmpty()) {
+ return
+ }
+ if (!isProbablyUtf8(bytes)) {
+ suppressed = true
+ System.err.println("(binary body omitted)")
+ return
+ }
+ for (byte in bytes) {
+ writeToBuffer(byte.toInt() and 0xFF)
+ }
+ }
+
+ if (buffer.size() == 0) {
+ return
+ }
+
+ val line =
+ try {
+ decoder.decode(ByteBuffer.wrap(buffer.toByteArray()))
+ } catch (e: CharacterCodingException) {
+ "(omitted line is not valid $charset)"
+ }
+ buffer.reset()
+ System.err.println(line)
+ }
+}
+
+/** The maximum number of code points to sample when checking if content is probably UTF-8. */
+private const val PROBABLY_UTF8_CODE_POINT_LIMIT = 64
+
+/**
+ * The maximum number of bytes to accumulate before running the [isProbablyUtf8] check. UTF-8 code
+ * points are at most 4 bytes, so this accommodates [PROBABLY_UTF8_CODE_POINT_LIMIT] code points.
+ */
+private const val PROBABLY_UTF8_BYTE_LIMIT = PROBABLY_UTF8_CODE_POINT_LIMIT * 4
+
+/**
+ * Returns `true` if the given [bytes] probably contain human-readable UTF-8 text.
+ *
+ * Decodes up to [PROBABLY_UTF8_CODE_POINT_LIMIT] code points and returns `false` if any
+ * non-whitespace ISO control characters are found, or if the bytes are not valid UTF-8.
+ */
+private fun isProbablyUtf8(bytes: ByteArray): Boolean {
+ try {
+ val decoder =
+ StandardCharsets.UTF_8.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ val charBuffer = decoder.decode(ByteBuffer.wrap(bytes))
+ var codePointCount = 0
+ var i = 0
+ while (i < charBuffer.length && codePointCount < PROBABLY_UTF8_CODE_POINT_LIMIT) {
+ val codePoint = Character.codePointAt(charBuffer, i)
+ if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
+ return false
+ }
+ i += Character.charCount(codePoint)
+ codePointCount++
+ }
+ return true
+ } catch (e: CharacterCodingException) {
+ return false
+ }
+}
+
+/** Returns the [Charset] in the given [contentType] string, or `null` if unspecified. */
+private fun parseCharset(contentType: String?): Charset? =
+ contentType
+ ?.split(";")
+ ?.drop(1)
+ ?.map { it.trim() }
+ ?.firstOrNull { it.startsWith("charset=", ignoreCase = true) }
+ ?.substringAfter("=")
+ ?.trim()
+ ?.removeSurrounding("\"")
+ ?.let { runCatching { charset(it) }.getOrNull() }
+
+/** Formats the [Duration] into a string like "1m 40s 467ms". */
+private fun Duration.format(): String =
+ toKotlinDuration().toComponents { days, hours, minutes, seconds, nanoseconds ->
+ buildString {
+ val milliseconds = nanoseconds / 1_000_000
+ if (days > 0) {
+ append("${days}d")
+ }
+ if (hours > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${hours}h")
+ }
+ if (minutes > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${minutes}m")
+ }
+ if (seconds > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${seconds}s")
+ }
+ if (milliseconds > 0) {
+ if (isNotEmpty()) {
+ append(" ")
+ }
+ append("${milliseconds}ms")
+ }
+
+ if (isEmpty()) {
+ append("0s")
+ }
+ }
+ }
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 b20ac9d..b6cc034 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,336 +2,2897 @@
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.core.toImmutable
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
+@JsonDeserialize(using = ModelConfig.Deserializer::class)
+@JsonSerialize(using = ModelConfig.Serializer::class)
class ModelConfig
-@JsonCreator(mode = JsonCreator.Mode.DISABLED)
private constructor(
- private val modelName: JsonField,
- private val apiKey: JsonField,
- private val baseUrl: JsonField,
- private val headers: JsonField,
- private val provider: JsonField,
- private val additionalProperties: MutableMap,
+ private val vertexModelConfigObject: VertexModelConfigObject? = null,
+ private val genericModelConfigObject: GenericModelConfigObject? = null,
+ private val _json: JsonValue? = null,
) {
- @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("headers") @ExcludeMissing headers: JsonField = JsonMissing.of(),
- @JsonProperty("provider") @ExcludeMissing provider: JsonField = JsonMissing.of(),
- ) : this(modelName, apiKey, baseUrl, headers, provider, mutableMapOf())
+ fun vertexModelConfigObject(): Optional =
+ Optional.ofNullable(vertexModelConfigObject)
- /**
- * Model name string with provider prefix (e.g., 'openai/gpt-5-nano')
- *
- * @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 modelName(): String = modelName.getRequired("modelName")
+ fun genericModelConfigObject(): Optional =
+ Optional.ofNullable(genericModelConfigObject)
- /**
- * 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 isVertexModelConfigObject(): Boolean = vertexModelConfigObject != null
- /**
- * 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")
+ fun isGenericModelConfigObject(): Boolean = genericModelConfigObject != null
- /**
- * Custom headers sent with every request to the model provider
- *
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
- */
- fun headers(): Optional = headers.getOptional("headers")
+ fun asVertexModelConfigObject(): VertexModelConfigObject =
+ vertexModelConfigObject.getOrThrow("vertexModelConfigObject")
+
+ fun asGenericModelConfigObject(): GenericModelConfigObject =
+ genericModelConfigObject.getOrThrow("genericModelConfigObject")
+
+ fun _json(): Optional = Optional.ofNullable(_json)
/**
- * AI provider for the model (or provide a baseURL endpoint instead)
+ * Maps this instance's current variant to a value of type [T] using the given [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the API, unless
+ * [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of the
+ * SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```java
+ * import com.browserbase.api.core.JsonValue;
+ * import java.util.Optional;
+ *
+ * Optional result = modelConfig.accept(new ModelConfig.Visitor>() {
+ * @Override
+ * public Optional visitVertexModelConfigObject(VertexModelConfigObject vertexModelConfigObject) {
+ * return Optional.of(vertexModelConfigObject.toString());
+ * }
*
- * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if the
- * server responded with an unexpected value).
+ * // ...
+ *
+ * @Override
+ * public Optional unknown(JsonValue json) {
+ * // Or inspect the `json`.
+ * return Optional.empty();
+ * }
+ * });
+ * ```
+ *
+ * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and
+ * the current variant is unknown.
*/
- fun provider(): Optional = provider.getOptional("provider")
+ fun accept(visitor: Visitor): T =
+ when {
+ vertexModelConfigObject != null ->
+ visitor.visitVertexModelConfigObject(vertexModelConfigObject)
+ genericModelConfigObject != null ->
+ visitor.visitGenericModelConfigObject(genericModelConfigObject)
+ else -> visitor.unknown(_json)
+ }
+
+ private var validated: Boolean = false
/**
- * Returns the raw JSON value of [modelName].
+ * Validates that the types of all values in this object match their expected types recursively.
*
- * Unlike [modelName], this method doesn't throw if the JSON field has an unexpected type.
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't match its
+ * expected type.
*/
- @JsonProperty("modelName") @ExcludeMissing fun _modelName(): JsonField = modelName
+ fun validate(): ModelConfig = apply {
+ if (validated) {
+ return@apply
+ }
+
+ accept(
+ object : Visitor {
+ override fun visitVertexModelConfigObject(
+ vertexModelConfigObject: VertexModelConfigObject
+ ) {
+ vertexModelConfigObject.validate()
+ }
+
+ override fun visitGenericModelConfigObject(
+ genericModelConfigObject: GenericModelConfigObject
+ ) {
+ genericModelConfigObject.validate()
+ }
+ }
+ )
+ validated = true
+ }
+
+ fun isValid(): Boolean =
+ try {
+ validate()
+ true
+ } catch (e: StagehandInvalidDataException) {
+ false
+ }
/**
- * Returns the raw JSON value of [apiKey].
+ * Returns a score indicating how many valid values are contained in this object recursively.
*
- * Unlike [apiKey], this method doesn't throw if the JSON field has an unexpected type.
+ * Used for best match union deserialization.
*/
- @JsonProperty("apiKey") @ExcludeMissing fun _apiKey(): JsonField = apiKey
+ @JvmSynthetic
+ internal fun validity(): Int =
+ accept(
+ object : Visitor {
+ override fun visitVertexModelConfigObject(
+ vertexModelConfigObject: VertexModelConfigObject
+ ) = vertexModelConfigObject.validity()
+
+ override fun visitGenericModelConfigObject(
+ genericModelConfigObject: GenericModelConfigObject
+ ) = genericModelConfigObject.validity()
+
+ override fun unknown(json: JsonValue?) = 0
+ }
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is ModelConfig &&
+ vertexModelConfigObject == other.vertexModelConfigObject &&
+ genericModelConfigObject == other.genericModelConfigObject
+ }
+
+ override fun hashCode(): Int = Objects.hash(vertexModelConfigObject, genericModelConfigObject)
+
+ override fun toString(): String =
+ when {
+ vertexModelConfigObject != null ->
+ "ModelConfig{vertexModelConfigObject=$vertexModelConfigObject}"
+ genericModelConfigObject != null ->
+ "ModelConfig{genericModelConfigObject=$genericModelConfigObject}"
+ _json != null -> "ModelConfig{_unknown=$_json}"
+ else -> throw IllegalStateException("Invalid ModelConfig")
+ }
+
+ companion object {
+
+ @JvmStatic
+ fun ofVertexModelConfigObject(vertexModelConfigObject: VertexModelConfigObject) =
+ ModelConfig(vertexModelConfigObject = vertexModelConfigObject)
+
+ @JvmStatic
+ fun ofGenericModelConfigObject(genericModelConfigObject: GenericModelConfigObject) =
+ ModelConfig(genericModelConfigObject = genericModelConfigObject)
+ }
/**
- * Returns the raw JSON value of [baseUrl].
- *
- * Unlike [baseUrl], this method doesn't throw if the JSON field has an unexpected type.
+ * An interface that defines how to map each variant of [ModelConfig] to a value of type [T].
*/
- @JsonProperty("baseURL") @ExcludeMissing fun _baseUrl(): JsonField = baseUrl
+ interface Visitor {
+
+ fun visitVertexModelConfigObject(vertexModelConfigObject: VertexModelConfigObject): T
+
+ fun visitGenericModelConfigObject(genericModelConfigObject: GenericModelConfigObject): T
+
+ /**
+ * Maps an unknown variant of [ModelConfig] to a value of type [T].
+ *
+ * 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 unknown(json: JsonValue?): T {
+ throw StagehandInvalidDataException("Unknown ModelConfig: $json")
+ }
+ }
+
+ 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(vertexModelConfigObject = it, _json = json)
+ },
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ ModelConfig(genericModelConfigObject = 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 boolean).
+ 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()
+ }
+ }
+ }
+
+ internal class Serializer : BaseSerializer(ModelConfig::class) {
+
+ override fun serialize(
+ value: ModelConfig,
+ generator: JsonGenerator,
+ provider: SerializerProvider,
+ ) {
+ when {
+ value.vertexModelConfigObject != null ->
+ generator.writeObject(value.vertexModelConfigObject)
+ value.genericModelConfigObject != null ->
+ generator.writeObject(value.genericModelConfigObject)
+ value._json != null -> generator.writeObject(value._json)
+ else -> throw IllegalStateException("Invalid ModelConfig")
+ }
+ }
+ }
+
+ class VertexModelConfigObject
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val auth: JsonField,
+ private val modelName: JsonField,
+ private val provider: JsonValue,
+ private val providerOptions: JsonField,
+ private val apiKey: JsonField,
+ private val baseUrl: JsonField,
+ private val headers: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("auth") @ExcludeMissing auth: JsonField = JsonMissing.of(),
+ @JsonProperty("modelName")
+ @ExcludeMissing
+ modelName: JsonField = JsonMissing.of(),
+ @JsonProperty("provider") @ExcludeMissing provider: JsonValue = JsonMissing.of(),
+ @JsonProperty("providerOptions")
+ @ExcludeMissing
+ providerOptions: JsonField = JsonMissing.of(),
+ @JsonProperty("apiKey") @ExcludeMissing apiKey: JsonField = JsonMissing.of(),
+ @JsonProperty("baseURL") @ExcludeMissing baseUrl: JsonField = JsonMissing.of(),
+ @JsonProperty("headers") @ExcludeMissing headers: JsonField = JsonMissing.of(),
+ ) : this(
+ auth,
+ modelName,
+ provider,
+ providerOptions,
+ apiKey,
+ baseUrl,
+ headers,
+ mutableMapOf(),
+ )
+
+ /**
+ * Vertex provider authentication configuration
+ *
+ * @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 auth(): Auth = auth.getRequired("auth")
+
+ /**
+ * Model name string with provider prefix (e.g., 'openai/gpt-5-nano')
+ *
+ * @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 modelName(): String = modelName.getRequired("modelName")
+
+ /**
+ * Vertex AI model provider
+ *
+ * Expected to always return the following:
+ * ```java
+ * JsonValue.from("vertex")
+ * ```
+ *
+ * However, this method can be useful for debugging and logging (e.g. if the server
+ * responded with an unexpected value).
+ */
+ @JsonProperty("provider") @ExcludeMissing fun _provider(): JsonValue = provider
+
+ /**
+ * Vertex provider-specific model configuration
+ *
+ * @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 providerOptions(): ProviderOptions = providerOptions.getRequired("providerOptions")
+
+ /**
+ * 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")
+
+ /**
+ * 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")
+
+ /**
+ * Custom headers sent with every request to the model provider
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g. if
+ * the server responded with an unexpected value).
+ */
+ fun headers(): Optional = headers.getOptional("headers")
+
+ /**
+ * Returns the raw JSON value of [auth].
+ *
+ * Unlike [auth], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("auth") @ExcludeMissing fun _auth(): JsonField = auth
+
+ /**
+ * 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 the raw JSON value of [providerOptions].
+ *
+ * Unlike [providerOptions], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("providerOptions")
+ @ExcludeMissing
+ fun _providerOptions(): JsonField = providerOptions
+
+ /**
+ * 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
+
+ /**
+ * 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 the raw JSON value of [headers].
+ *
+ * Unlike [headers], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("headers") @ExcludeMissing fun _headers(): JsonField = headers
+
+ @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 [VertexModelConfigObject].
+ *
+ * The following fields are required:
+ * ```java
+ * .auth()
+ * .modelName()
+ * .providerOptions()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [VertexModelConfigObject]. */
+ class Builder internal constructor() {
+
+ private var auth: JsonField? = null
+ private var modelName: JsonField? = null
+ private var provider: JsonValue = JsonValue.from("vertex")
+ private var providerOptions: JsonField? = null
+ private var apiKey: JsonField = JsonMissing.of()
+ private var baseUrl: JsonField = JsonMissing.of()
+ private var headers: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(vertexModelConfigObject: VertexModelConfigObject) = apply {
+ auth = vertexModelConfigObject.auth
+ modelName = vertexModelConfigObject.modelName
+ provider = vertexModelConfigObject.provider
+ providerOptions = vertexModelConfigObject.providerOptions
+ apiKey = vertexModelConfigObject.apiKey
+ baseUrl = vertexModelConfigObject.baseUrl
+ headers = vertexModelConfigObject.headers
+ additionalProperties = vertexModelConfigObject.additionalProperties.toMutableMap()
+ }
+
+ /** Vertex provider authentication configuration */
+ fun auth(auth: Auth) = auth(JsonField.of(auth))
+
+ /**
+ * Sets [Builder.auth] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.auth] with a well-typed [Auth] value instead. This
+ * method is primarily for setting the field to an undocumented or not yet supported
+ * value.
+ */
+ fun auth(auth: JsonField) = apply { this.auth = auth }
+
+ /** Model name string with provider prefix (e.g., 'openai/gpt-5-nano') */
+ 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 }
+
+ /**
+ * Sets the field to an arbitrary JSON value.
+ *
+ * It is usually unnecessary to call this method because the field defaults to the
+ * following:
+ * ```java
+ * JsonValue.from("vertex")
+ * ```
+ *
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun provider(provider: JsonValue) = apply { this.provider = provider }
+
+ /** Vertex provider-specific model configuration */
+ fun providerOptions(providerOptions: ProviderOptions) =
+ providerOptions(JsonField.of(providerOptions))
+
+ /**
+ * Sets [Builder.providerOptions] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.providerOptions] with a well-typed [ProviderOptions]
+ * value instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun providerOptions(providerOptions: JsonField) = apply {
+ this.providerOptions = providerOptions
+ }
+
+ /** 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 }
+
+ /** Custom headers sent with every request to the model provider */
+ fun headers(headers: Headers) = headers(JsonField.of(headers))
+
+ /**
+ * Sets [Builder.headers] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.headers] with a well-typed [Headers] value instead.
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun headers(headers: JsonField) = apply { this.headers = headers }
+
+ 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 [VertexModelConfigObject].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .auth()
+ * .modelName()
+ * .providerOptions()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): VertexModelConfigObject =
+ VertexModelConfigObject(
+ checkRequired("auth", auth),
+ checkRequired("modelName", modelName),
+ provider,
+ checkRequired("providerOptions", providerOptions),
+ apiKey,
+ baseUrl,
+ headers,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't match its
+ * expected type.
+ */
+ fun validate(): VertexModelConfigObject = apply {
+ if (validated) {
+ return@apply
+ }
+
+ auth().validate()
+ modelName()
+ _provider().let {
+ if (it != JsonValue.from("vertex")) {
+ throw StagehandInvalidDataException("'provider' is invalid, received $it")
+ }
+ }
+ providerOptions().validate()
+ apiKey()
+ baseUrl()
+ headers().ifPresent { it.validate() }
+ 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 =
+ (auth.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (modelName.asKnown().isPresent) 1 else 0) +
+ provider.let { if (it == JsonValue.from("vertex")) 1 else 0 } +
+ (providerOptions.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (apiKey.asKnown().isPresent) 1 else 0) +
+ (if (baseUrl.asKnown().isPresent) 1 else 0) +
+ (headers.asKnown().getOrNull()?.validity() ?: 0)
+
+ /** Vertex provider authentication configuration */
+ class Auth
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val credentials: JsonField,
+ private val type: JsonValue,
+ private val projectId: JsonField,
+ private val scopes: JsonField,
+ private val universeDomain: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("credentials")
+ @ExcludeMissing
+ credentials: JsonField = JsonMissing.of(),
+ @JsonProperty("type") @ExcludeMissing type: JsonValue = JsonMissing.of(),
+ @JsonProperty("projectId")
+ @ExcludeMissing
+ projectId: JsonField = JsonMissing.of(),
+ @JsonProperty("scopes")
+ @ExcludeMissing
+ scopes: JsonField = JsonMissing.of(),
+ @JsonProperty("universeDomain")
+ @ExcludeMissing
+ universeDomain: JsonField = JsonMissing.of(),
+ ) : this(credentials, type, projectId, scopes, universeDomain, mutableMapOf())
+
+ /**
+ * Google Cloud service account credentials
+ *
+ * @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 credentials(): Credentials = credentials.getRequired("credentials")
+
+ /**
+ * Use inline Google Cloud service account credentials for provider authentication
+ *
+ * Expected to always return the following:
+ * ```java
+ * JsonValue.from("googleServiceAccount")
+ * ```
+ *
+ * However, this method can be useful for debugging and logging (e.g. if the server
+ * responded with an unexpected value).
+ */
+ @JsonProperty("type") @ExcludeMissing fun _type(): JsonValue = type
+
+ /**
+ * Google Cloud project ID used by google-auth-library
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g.
+ * if the server responded with an unexpected value).
+ */
+ fun projectId(): Optional = projectId.getOptional("projectId")
+
+ /**
+ * Google auth scopes for the desired API request
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g.
+ * if the server responded with an unexpected value).
+ */
+ fun scopes(): Optional = scopes.getOptional("scopes")
+
+ /**
+ * Google Cloud universe domain
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type (e.g.
+ * if the server responded with an unexpected value).
+ */
+ fun universeDomain(): Optional = universeDomain.getOptional("universeDomain")
+
+ /**
+ * Returns the raw JSON value of [credentials].
+ *
+ * Unlike [credentials], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("credentials")
+ @ExcludeMissing
+ fun _credentials(): JsonField = credentials
+
+ /**
+ * Returns the raw JSON value of [projectId].
+ *
+ * Unlike [projectId], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("projectId")
+ @ExcludeMissing
+ fun _projectId(): JsonField = projectId
+
+ /**
+ * Returns the raw JSON value of [scopes].
+ *
+ * Unlike [scopes], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("scopes") @ExcludeMissing fun _scopes(): JsonField = scopes
+
+ /**
+ * Returns the raw JSON value of [universeDomain].
+ *
+ * Unlike [universeDomain], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("universeDomain")
+ @ExcludeMissing
+ fun _universeDomain(): JsonField = universeDomain
+
+ @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 [Auth].
+ *
+ * The following fields are required:
+ * ```java
+ * .credentials()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Auth]. */
+ class Builder internal constructor() {
+
+ private var credentials: JsonField? = null
+ private var type: JsonValue = JsonValue.from("googleServiceAccount")
+ private var projectId: JsonField = JsonMissing.of()
+ private var scopes: JsonField = JsonMissing.of()
+ private var universeDomain: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(auth: Auth) = apply {
+ credentials = auth.credentials
+ type = auth.type
+ projectId = auth.projectId
+ scopes = auth.scopes
+ universeDomain = auth.universeDomain
+ additionalProperties = auth.additionalProperties.toMutableMap()
+ }
+
+ /** Google Cloud service account credentials */
+ fun credentials(credentials: Credentials) = credentials(JsonField.of(credentials))
+
+ /**
+ * Sets [Builder.credentials] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.credentials] with a well-typed [Credentials]
+ * value instead. This method is primarily for setting the field to an undocumented
+ * or not yet supported value.
+ */
+ fun credentials(credentials: JsonField) = apply {
+ this.credentials = credentials
+ }
+
+ /**
+ * Sets the field to an arbitrary JSON value.
+ *
+ * It is usually unnecessary to call this method because the field defaults to the
+ * following:
+ * ```java
+ * JsonValue.from("googleServiceAccount")
+ * ```
+ *
+ * This method is primarily for setting the field to an undocumented or not yet
+ * supported value.
+ */
+ fun type(type: JsonValue) = apply { this.type = type }
+
+ /** Google Cloud project ID used by google-auth-library */
+ fun projectId(projectId: String) = projectId(JsonField.of(projectId))
+
+ /**
+ * Sets [Builder.projectId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.projectId] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or not
+ * yet supported value.
+ */
+ fun projectId(projectId: JsonField) = apply { this.projectId = projectId }
+
+ /** Google auth scopes for the desired API request */
+ fun scopes(scopes: Scopes) = scopes(JsonField.of(scopes))
+
+ /**
+ * Sets [Builder.scopes] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.scopes] with a well-typed [Scopes] value
+ * instead. This method is primarily for setting the field to an undocumented or not
+ * yet supported value.
+ */
+ fun scopes(scopes: JsonField) = apply { this.scopes = scopes }
+
+ /** Alias for calling [scopes] with `Scopes.ofString(string)`. */
+ fun scopes(string: String) = scopes(Scopes.ofString(string))
+
+ /** Alias for calling [scopes] with `Scopes.ofStrings(strings)`. */
+ fun scopesOfStrings(strings: List) = scopes(Scopes.ofStrings(strings))
+
+ /** Google Cloud universe domain */
+ fun universeDomain(universeDomain: String) =
+ universeDomain(JsonField.of(universeDomain))
+
+ /**
+ * Sets [Builder.universeDomain] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.universeDomain] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or not
+ * yet supported value.
+ */
+ fun universeDomain(universeDomain: JsonField) = apply {
+ this.universeDomain = universeDomain
+ }
+
+ 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 [Auth].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .credentials()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Auth =
+ Auth(
+ checkRequired("credentials", credentials),
+ type,
+ projectId,
+ scopes,
+ universeDomain,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
+ fun validate(): Auth = apply {
+ if (validated) {
+ return@apply
+ }
+
+ credentials().validate()
+ _type().let {
+ if (it != JsonValue.from("googleServiceAccount")) {
+ throw StagehandInvalidDataException("'type' is invalid, received $it")
+ }
+ }
+ projectId()
+ scopes().ifPresent { it.validate() }
+ universeDomain()
+ 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 =
+ (credentials.asKnown().getOrNull()?.validity() ?: 0) +
+ type.let { if (it == JsonValue.from("googleServiceAccount")) 1 else 0 } +
+ (if (projectId.asKnown().isPresent) 1 else 0) +
+ (scopes.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (universeDomain.asKnown().isPresent) 1 else 0)
+
+ /** Google Cloud service account credentials */
+ class Credentials
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val clientEmail: JsonField,
+ private val privateKey: JsonField,
+ private val authProviderX509CertUrl: JsonField,
+ private val authUri: JsonField,
+ private val clientId: JsonField,
+ private val clientX509CertUrl: JsonField,
+ private val privateKeyId: JsonField,
+ private val projectId: JsonField,
+ private val tokenUri: JsonField,
+ private val type: JsonField,
+ private val universeDomain: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("client_email")
+ @ExcludeMissing
+ clientEmail: JsonField = JsonMissing.of(),
+ @JsonProperty("private_key")
+ @ExcludeMissing
+ privateKey: JsonField = JsonMissing.of(),
+ @JsonProperty("auth_provider_x509_cert_url")
+ @ExcludeMissing
+ authProviderX509CertUrl: JsonField = JsonMissing.of(),
+ @JsonProperty("auth_uri")
+ @ExcludeMissing
+ authUri: JsonField = JsonMissing.of(),
+ @JsonProperty("client_id")
+ @ExcludeMissing
+ clientId: JsonField = JsonMissing.of(),
+ @JsonProperty("client_x509_cert_url")
+ @ExcludeMissing
+ clientX509CertUrl: JsonField = JsonMissing.of(),
+ @JsonProperty("private_key_id")
+ @ExcludeMissing
+ privateKeyId: JsonField = JsonMissing.of(),
+ @JsonProperty("project_id")
+ @ExcludeMissing
+ projectId: JsonField = JsonMissing.of(),
+ @JsonProperty("token_uri")
+ @ExcludeMissing
+ tokenUri: JsonField = JsonMissing.of(),
+ @JsonProperty("type") @ExcludeMissing type: JsonField = JsonMissing.of(),
+ @JsonProperty("universe_domain")
+ @ExcludeMissing
+ universeDomain: JsonField = JsonMissing.of(),
+ ) : this(
+ clientEmail,
+ privateKey,
+ authProviderX509CertUrl,
+ authUri,
+ clientId,
+ clientX509CertUrl,
+ privateKeyId,
+ projectId,
+ tokenUri,
+ type,
+ universeDomain,
+ 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 clientEmail(): String = clientEmail.getRequired("client_email")
+
+ /**
+ * @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 privateKey(): String = privateKey.getRequired("private_key")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun authProviderX509CertUrl(): Optional =
+ authProviderX509CertUrl.getOptional("auth_provider_x509_cert_url")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun authUri(): Optional = authUri.getOptional("auth_uri")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun clientId(): Optional = clientId.getOptional("client_id")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun clientX509CertUrl(): Optional =
+ clientX509CertUrl.getOptional("client_x509_cert_url")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun privateKeyId(): Optional = privateKeyId.getOptional("private_key_id")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun projectId(): Optional = projectId.getOptional("project_id")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun tokenUri(): Optional = tokenUri.getOptional("token_uri")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun type(): Optional = type.getOptional("type")
+
+ /**
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun universeDomain(): Optional =
+ universeDomain.getOptional("universe_domain")
+
+ /**
+ * Returns the raw JSON value of [clientEmail].
+ *
+ * Unlike [clientEmail], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("client_email")
+ @ExcludeMissing
+ fun _clientEmail(): JsonField = clientEmail
+
+ /**
+ * Returns the raw JSON value of [privateKey].
+ *
+ * Unlike [privateKey], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("private_key")
+ @ExcludeMissing
+ fun _privateKey(): JsonField = privateKey
+
+ /**
+ * Returns the raw JSON value of [authProviderX509CertUrl].
+ *
+ * Unlike [authProviderX509CertUrl], this method doesn't throw if the JSON field has
+ * an unexpected type.
+ */
+ @JsonProperty("auth_provider_x509_cert_url")
+ @ExcludeMissing
+ fun _authProviderX509CertUrl(): JsonField = authProviderX509CertUrl
+
+ /**
+ * Returns the raw JSON value of [authUri].
+ *
+ * Unlike [authUri], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("auth_uri")
+ @ExcludeMissing
+ fun _authUri(): JsonField = authUri
+
+ /**
+ * Returns the raw JSON value of [clientId].
+ *
+ * Unlike [clientId], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("client_id")
+ @ExcludeMissing
+ fun _clientId(): JsonField = clientId
+
+ /**
+ * Returns the raw JSON value of [clientX509CertUrl].
+ *
+ * Unlike [clientX509CertUrl], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("client_x509_cert_url")
+ @ExcludeMissing
+ fun _clientX509CertUrl(): JsonField = clientX509CertUrl
+
+ /**
+ * Returns the raw JSON value of [privateKeyId].
+ *
+ * Unlike [privateKeyId], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("private_key_id")
+ @ExcludeMissing
+ fun _privateKeyId(): JsonField = privateKeyId
+
+ /**
+ * Returns the raw JSON value of [projectId].
+ *
+ * Unlike [projectId], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("project_id")
+ @ExcludeMissing
+ fun _projectId(): JsonField = projectId
+
+ /**
+ * Returns the raw JSON value of [tokenUri].
+ *
+ * Unlike [tokenUri], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("token_uri")
+ @ExcludeMissing
+ fun _tokenUri(): JsonField = tokenUri
+
+ /**
+ * Returns the raw JSON value of [type].
+ *
+ * Unlike [type], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("type") @ExcludeMissing fun _type(): JsonField = type
+
+ /**
+ * Returns the raw JSON value of [universeDomain].
+ *
+ * Unlike [universeDomain], this method doesn't throw if the JSON field has an
+ * unexpected type.
+ */
+ @JsonProperty("universe_domain")
+ @ExcludeMissing
+ fun _universeDomain(): JsonField = universeDomain
+
+ @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 [Credentials].
+ *
+ * The following fields are required:
+ * ```java
+ * .clientEmail()
+ * .privateKey()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Credentials]. */
+ class Builder internal constructor() {
+
+ private var clientEmail: JsonField? = null
+ private var privateKey: JsonField? = null
+ private var authProviderX509CertUrl: JsonField = JsonMissing.of()
+ private var authUri: JsonField = JsonMissing.of()
+ private var clientId: JsonField = JsonMissing.of()
+ private var clientX509CertUrl: JsonField = JsonMissing.of()
+ private var privateKeyId: JsonField = JsonMissing.of()
+ private var projectId: JsonField = JsonMissing.of()
+ private var tokenUri: JsonField = JsonMissing.of()
+ private var type: JsonField = JsonMissing.of()
+ private var universeDomain: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(credentials: Credentials) = apply {
+ clientEmail = credentials.clientEmail
+ privateKey = credentials.privateKey
+ authProviderX509CertUrl = credentials.authProviderX509CertUrl
+ authUri = credentials.authUri
+ clientId = credentials.clientId
+ clientX509CertUrl = credentials.clientX509CertUrl
+ privateKeyId = credentials.privateKeyId
+ projectId = credentials.projectId
+ tokenUri = credentials.tokenUri
+ type = credentials.type
+ universeDomain = credentials.universeDomain
+ additionalProperties = credentials.additionalProperties.toMutableMap()
+ }
+
+ fun clientEmail(clientEmail: String) = clientEmail(JsonField.of(clientEmail))
+
+ /**
+ * Sets [Builder.clientEmail] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.clientEmail] with a well-typed [String]
+ * value instead. This method is primarily for setting the field to an
+ * undocumented or not yet supported value.
+ */
+ fun clientEmail(clientEmail: JsonField) = apply {
+ this.clientEmail = clientEmail
+ }
+
+ fun privateKey(privateKey: String) = privateKey(JsonField.of(privateKey))
+
+ /**
+ * Sets [Builder.privateKey] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.privateKey] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun privateKey(privateKey: JsonField) = apply {
+ this.privateKey = privateKey
+ }
+
+ fun authProviderX509CertUrl(authProviderX509CertUrl: String) =
+ authProviderX509CertUrl(JsonField.of(authProviderX509CertUrl))
+
+ /**
+ * Sets [Builder.authProviderX509CertUrl] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.authProviderX509CertUrl] with a well-typed
+ * [String] value instead. This method is primarily for setting the field to an
+ * undocumented or not yet supported value.
+ */
+ fun authProviderX509CertUrl(authProviderX509CertUrl: JsonField) =
+ apply {
+ this.authProviderX509CertUrl = authProviderX509CertUrl
+ }
+
+ fun authUri(authUri: String) = authUri(JsonField.of(authUri))
+
+ /**
+ * Sets [Builder.authUri] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.authUri] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun authUri(authUri: JsonField) = apply { this.authUri = authUri }
+
+ fun clientId(clientId: String) = clientId(JsonField.of(clientId))
+
+ /**
+ * Sets [Builder.clientId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.clientId] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun clientId(clientId: JsonField) = apply { this.clientId = clientId }
+
+ fun clientX509CertUrl(clientX509CertUrl: String) =
+ clientX509CertUrl(JsonField.of(clientX509CertUrl))
+
+ /**
+ * Sets [Builder.clientX509CertUrl] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.clientX509CertUrl] with a well-typed
+ * [String] value instead. This method is primarily for setting the field to an
+ * undocumented or not yet supported value.
+ */
+ fun clientX509CertUrl(clientX509CertUrl: JsonField) = apply {
+ this.clientX509CertUrl = clientX509CertUrl
+ }
+
+ fun privateKeyId(privateKeyId: String) =
+ privateKeyId(JsonField.of(privateKeyId))
+
+ /**
+ * Sets [Builder.privateKeyId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.privateKeyId] with a well-typed [String]
+ * value instead. This method is primarily for setting the field to an
+ * undocumented or not yet supported value.
+ */
+ fun privateKeyId(privateKeyId: JsonField) = apply {
+ this.privateKeyId = privateKeyId
+ }
+
+ fun projectId(projectId: String) = projectId(JsonField.of(projectId))
+
+ /**
+ * Sets [Builder.projectId] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.projectId] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun projectId(projectId: JsonField) = apply {
+ this.projectId = projectId
+ }
+
+ fun tokenUri(tokenUri: String) = tokenUri(JsonField.of(tokenUri))
+
+ /**
+ * Sets [Builder.tokenUri] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.tokenUri] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun tokenUri(tokenUri: JsonField) = apply { this.tokenUri = tokenUri }
+
+ fun type(type: Type) = type(JsonField.of(type))
+
+ /**
+ * Sets [Builder.type] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.type] with a well-typed [Type] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun type(type: JsonField) = apply { this.type = type }
+
+ fun universeDomain(universeDomain: String) =
+ universeDomain(JsonField.of(universeDomain))
+
+ /**
+ * Sets [Builder.universeDomain] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.universeDomain] with a well-typed [String]
+ * value instead. This method is primarily for setting the field to an
+ * undocumented or not yet supported value.
+ */
+ fun universeDomain(universeDomain: JsonField) = apply {
+ this.universeDomain = universeDomain
+ }
+
+ 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 [Credentials].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .clientEmail()
+ * .privateKey()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Credentials =
+ Credentials(
+ checkRequired("clientEmail", clientEmail),
+ checkRequired("privateKey", privateKey),
+ authProviderX509CertUrl,
+ authUri,
+ clientId,
+ clientX509CertUrl,
+ privateKeyId,
+ projectId,
+ tokenUri,
+ type,
+ universeDomain,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
+ fun validate(): Credentials = apply {
+ if (validated) {
+ return@apply
+ }
+
+ clientEmail()
+ privateKey()
+ authProviderX509CertUrl()
+ authUri()
+ clientId()
+ clientX509CertUrl()
+ privateKeyId()
+ projectId()
+ tokenUri()
+ type().ifPresent { it.validate() }
+ universeDomain()
+ 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 (clientEmail.asKnown().isPresent) 1 else 0) +
+ (if (privateKey.asKnown().isPresent) 1 else 0) +
+ (if (authProviderX509CertUrl.asKnown().isPresent) 1 else 0) +
+ (if (authUri.asKnown().isPresent) 1 else 0) +
+ (if (clientId.asKnown().isPresent) 1 else 0) +
+ (if (clientX509CertUrl.asKnown().isPresent) 1 else 0) +
+ (if (privateKeyId.asKnown().isPresent) 1 else 0) +
+ (if (projectId.asKnown().isPresent) 1 else 0) +
+ (if (tokenUri.asKnown().isPresent) 1 else 0) +
+ (type.asKnown().getOrNull()?.validity() ?: 0) +
+ (if (universeDomain.asKnown().isPresent) 1 else 0)
+
+ class Type @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 SERVICE_ACCOUNT = of("service_account")
+
+ @JvmStatic fun of(value: String) = Type(JsonField.of(value))
+ }
+
+ /** An enum containing [Type]'s known values. */
+ enum class Known {
+ SERVICE_ACCOUNT
+ }
+
+ /**
+ * An enum containing [Type]'s known values, as well as an [_UNKNOWN] member.
+ *
+ * An instance of [Type] 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 {
+ SERVICE_ACCOUNT,
+ /**
+ * An enum member indicating that [Type] 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) {
+ SERVICE_ACCOUNT -> Value.SERVICE_ACCOUNT
+ 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) {
+ SERVICE_ACCOUNT -> Known.SERVICE_ACCOUNT
+ else -> throw StagehandInvalidDataException("Unknown Type: $value")
+ }
+
+ /**
+ * Returns this class instance's primitive wire representation.
+ *
+ * This differs from the [toString] method because that method is primarily for
+ * debugging and generally doesn't throw.
+ *
+ * @throws StagehandInvalidDataException if this class instance's value does not
+ * have the expected primitive type.
+ */
+ fun asString(): String =
+ _value().asString().orElseThrow {
+ StagehandInvalidDataException("Value is not a String")
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected
+ * types recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for
+ * existing fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object
+ * doesn't match its expected type.
+ */
+ fun validate(): Type = 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 Type && 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 Credentials &&
+ clientEmail == other.clientEmail &&
+ privateKey == other.privateKey &&
+ authProviderX509CertUrl == other.authProviderX509CertUrl &&
+ authUri == other.authUri &&
+ clientId == other.clientId &&
+ clientX509CertUrl == other.clientX509CertUrl &&
+ privateKeyId == other.privateKeyId &&
+ projectId == other.projectId &&
+ tokenUri == other.tokenUri &&
+ type == other.type &&
+ universeDomain == other.universeDomain &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ clientEmail,
+ privateKey,
+ authProviderX509CertUrl,
+ authUri,
+ clientId,
+ clientX509CertUrl,
+ privateKeyId,
+ projectId,
+ tokenUri,
+ type,
+ universeDomain,
+ additionalProperties,
+ )
+ }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "Credentials{clientEmail=$clientEmail, privateKey=$privateKey, authProviderX509CertUrl=$authProviderX509CertUrl, authUri=$authUri, clientId=$clientId, clientX509CertUrl=$clientX509CertUrl, privateKeyId=$privateKeyId, projectId=$projectId, tokenUri=$tokenUri, type=$type, universeDomain=$universeDomain, additionalProperties=$additionalProperties}"
+ }
+
+ /** Google auth scopes for the desired API request */
+ @JsonDeserialize(using = Scopes.Deserializer::class)
+ @JsonSerialize(using = Scopes.Serializer::class)
+ class Scopes
+ private constructor(
+ private val string: String? = null,
+ private val strings: List? = null,
+ private val _json: JsonValue? = null,
+ ) {
+
+ fun string(): Optional = Optional.ofNullable(string)
+
+ fun strings(): Optional> = Optional.ofNullable(strings)
+
+ fun isString(): Boolean = string != null
+
+ fun isStrings(): Boolean = strings != null
+
+ fun asString(): String = string.getOrThrow("string")
+
+ fun asStrings(): List = strings.getOrThrow("strings")
+
+ fun _json(): Optional = Optional.ofNullable(_json)
+
+ /**
+ * Maps this instance's current variant to a value of type [T] using the given
+ * [visitor].
+ *
+ * Note that this method is _not_ forwards compatible with new variants from the
+ * API, unless [visitor] overrides [Visitor.unknown]. To handle variants not known
+ * to this version of the SDK gracefully, consider overriding [Visitor.unknown]:
+ * ```java
+ * import com.browserbase.api.core.JsonValue;
+ * import java.util.Optional;
+ *
+ * Optional result = scopes.accept(new Scopes.Visitor>() {
+ * @Override
+ * public Optional visitString(String string) {
+ * return Optional.of(string.toString());
+ * }
+ *
+ * // ...
+ *
+ * @Override
+ * public Optional unknown(JsonValue json) {
+ * // Or inspect the `json`.
+ * return Optional.empty();
+ * }
+ * });
+ * ```
+ *
+ * @throws StagehandInvalidDataException if [Visitor.unknown] is not overridden in
+ * [visitor] and the current variant is unknown.
+ */
+ fun accept(visitor: Visitor): T =
+ when {
+ string != null -> visitor.visitString(string)
+ strings != null -> visitor.visitStrings(strings)
+ else -> visitor.unknown(_json)
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
+ fun validate(): Scopes = apply {
+ if (validated) {
+ return@apply
+ }
+
+ accept(
+ object : Visitor {
+ override fun visitString(string: String) {}
+
+ override fun visitStrings(strings: List) {}
+ }
+ )
+ 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 =
+ accept(
+ object : Visitor {
+ override fun visitString(string: String) = 1
+
+ override fun visitStrings(strings: List) = strings.size
+
+ override fun unknown(json: JsonValue?) = 0
+ }
+ )
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is Scopes && string == other.string && strings == other.strings
+ }
+
+ override fun hashCode(): Int = Objects.hash(string, strings)
+
+ override fun toString(): String =
+ when {
+ string != null -> "Scopes{string=$string}"
+ strings != null -> "Scopes{strings=$strings}"
+ _json != null -> "Scopes{_unknown=$_json}"
+ else -> throw IllegalStateException("Invalid Scopes")
+ }
+
+ companion object {
+
+ @JvmStatic fun ofString(string: String) = Scopes(string = string)
+
+ @JvmStatic
+ fun ofStrings(strings: List) = Scopes(strings = strings.toImmutable())
+ }
+
+ /**
+ * An interface that defines how to map each variant of [Scopes] to a value of type
+ * [T].
+ */
+ interface Visitor {
+
+ fun visitString(string: String): T
+
+ fun visitStrings(strings: List): T
+
+ /**
+ * Maps an unknown variant of [Scopes] to a value of type [T].
+ *
+ * An instance of [Scopes] 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 unknown(json: JsonValue?): T {
+ throw StagehandInvalidDataException("Unknown Scopes: $json")
+ }
+ }
+
+ internal class Deserializer : BaseDeserializer(Scopes::class) {
+
+ override fun ObjectCodec.deserialize(node: JsonNode): Scopes {
+ val json = JsonValue.fromJsonNode(node)
+
+ val bestMatches =
+ sequenceOf(
+ tryDeserialize(node, jacksonTypeRef())?.let {
+ Scopes(string = it, _json = json)
+ },
+ tryDeserialize(node, jacksonTypeRef>())?.let {
+ Scopes(strings = 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
+ // boolean).
+ 0 -> Scopes(_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()
+ }
+ }
+ }
+
+ internal class Serializer : BaseSerializer(Scopes::class) {
+
+ override fun serialize(
+ value: Scopes,
+ generator: JsonGenerator,
+ provider: SerializerProvider,
+ ) {
+ when {
+ value.string != null -> generator.writeObject(value.string)
+ value.strings != null -> generator.writeObject(value.strings)
+ value._json != null -> generator.writeObject(value._json)
+ else -> throw IllegalStateException("Invalid Scopes")
+ }
+ }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is Auth &&
+ credentials == other.credentials &&
+ type == other.type &&
+ projectId == other.projectId &&
+ scopes == other.scopes &&
+ universeDomain == other.universeDomain &&
+ additionalProperties == other.additionalProperties
+ }
+
+ private val hashCode: Int by lazy {
+ Objects.hash(
+ credentials,
+ type,
+ projectId,
+ scopes,
+ universeDomain,
+ additionalProperties,
+ )
+ }
+
+ override fun hashCode(): Int = hashCode
+
+ override fun toString() =
+ "Auth{credentials=$credentials, type=$type, projectId=$projectId, scopes=$scopes, universeDomain=$universeDomain, additionalProperties=$additionalProperties}"
+ }
+
+ /** Vertex provider-specific model configuration */
+ class ProviderOptions
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val vertex: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("vertex") @ExcludeMissing vertex: JsonField = JsonMissing.of()
+ ) : this(vertex, mutableMapOf())
+
+ /**
+ * Vertex AI provider-specific settings
+ *
+ * @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 vertex(): Vertex = vertex.getRequired("vertex")
+
+ /**
+ * Returns the raw JSON value of [vertex].
+ *
+ * Unlike [vertex], this method doesn't throw if the JSON field has an unexpected type.
+ */
+ @JsonProperty("vertex") @ExcludeMissing fun _vertex(): JsonField = vertex
+
+ @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 [ProviderOptions].
+ *
+ * The following fields are required:
+ * ```java
+ * .vertex()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [ProviderOptions]. */
+ class Builder internal constructor() {
+
+ private var vertex: JsonField? = null
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(providerOptions: ProviderOptions) = apply {
+ vertex = providerOptions.vertex
+ additionalProperties = providerOptions.additionalProperties.toMutableMap()
+ }
+
+ /** Vertex AI provider-specific settings */
+ fun vertex(vertex: Vertex) = vertex(JsonField.of(vertex))
+
+ /**
+ * Sets [Builder.vertex] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.vertex] with a well-typed [Vertex] value
+ * instead. This method is primarily for setting the field to an undocumented or not
+ * yet supported value.
+ */
+ fun vertex(vertex: JsonField) = apply { this.vertex = vertex }
+
+ 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 [ProviderOptions].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .vertex()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): ProviderOptions =
+ ProviderOptions(
+ checkRequired("vertex", vertex),
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't match
+ * its expected type.
+ */
+ fun validate(): ProviderOptions = apply {
+ if (validated) {
+ return@apply
+ }
+
+ vertex().validate()
+ 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 = (vertex.asKnown().getOrNull()?.validity() ?: 0)
+
+ /** Vertex AI provider-specific settings */
+ class Vertex
+ @JsonCreator(mode = JsonCreator.Mode.DISABLED)
+ private constructor(
+ private val location: JsonField,
+ private val project: JsonField,
+ private val baseUrl: JsonField,
+ private val headers: JsonField,
+ private val additionalProperties: MutableMap,
+ ) {
+
+ @JsonCreator
+ private constructor(
+ @JsonProperty("location")
+ @ExcludeMissing
+ location: JsonField = JsonMissing.of(),
+ @JsonProperty("project")
+ @ExcludeMissing
+ project: JsonField = JsonMissing.of(),
+ @JsonProperty("baseURL")
+ @ExcludeMissing
+ baseUrl: JsonField = JsonMissing.of(),
+ @JsonProperty("headers")
+ @ExcludeMissing
+ headers: JsonField = JsonMissing.of(),
+ ) : this(location, project, baseUrl, headers, mutableMapOf())
+
+ /**
+ * Google Cloud location for Vertex AI models
+ *
+ * @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 location(): String = location.getRequired("location")
+
+ /**
+ * Google Cloud project ID for Vertex AI models
+ *
+ * @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 project(): String = project.getRequired("project")
+
+ /**
+ * Base URL for the Vertex AI 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")
+
+ /**
+ * Custom headers sent with every request to the Vertex AI provider
+ *
+ * @throws StagehandInvalidDataException if the JSON field has an unexpected type
+ * (e.g. if the server responded with an unexpected value).
+ */
+ fun headers(): Optional = headers.getOptional("headers")
+
+ /**
+ * Returns the raw JSON value of [location].
+ *
+ * Unlike [location], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("location")
+ @ExcludeMissing
+ fun _location(): JsonField = location
+
+ /**
+ * Returns the raw JSON value of [project].
+ *
+ * Unlike [project], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("project") @ExcludeMissing fun _project(): JsonField = project
+
+ /**
+ * 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 the raw JSON value of [headers].
+ *
+ * Unlike [headers], this method doesn't throw if the JSON field has an unexpected
+ * type.
+ */
+ @JsonProperty("headers")
+ @ExcludeMissing
+ fun _headers(): JsonField = headers
+
+ @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 [Vertex].
+ *
+ * The following fields are required:
+ * ```java
+ * .location()
+ * .project()
+ * ```
+ */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Vertex]. */
+ class Builder internal constructor() {
+
+ private var location: JsonField? = null
+ private var project: JsonField? = null
+ private var baseUrl: JsonField = JsonMissing.of()
+ private var headers: JsonField = JsonMissing.of()
+ private var additionalProperties: MutableMap = mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(vertex: Vertex) = apply {
+ location = vertex.location
+ project = vertex.project
+ baseUrl = vertex.baseUrl
+ headers = vertex.headers
+ additionalProperties = vertex.additionalProperties.toMutableMap()
+ }
+
+ /** Google Cloud location for Vertex AI models */
+ fun location(location: String) = location(JsonField.of(location))
+
+ /**
+ * Sets [Builder.location] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.location] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun location(location: JsonField) = apply { this.location = location }
+
+ /** Google Cloud project ID for Vertex AI models */
+ fun project(project: String) = project(JsonField.of(project))
+
+ /**
+ * Sets [Builder.project] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.project] with a well-typed [String] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun project(project: JsonField) = apply { this.project = project }
+
+ /** Base URL for the Vertex AI 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 }
+
+ /** Custom headers sent with every request to the Vertex AI provider */
+ fun headers(headers: Headers) = headers(JsonField.of(headers))
+
+ /**
+ * Sets [Builder.headers] to an arbitrary JSON value.
+ *
+ * You should usually call [Builder.headers] with a well-typed [Headers] value
+ * instead. This method is primarily for setting the field to an undocumented or
+ * not yet supported value.
+ */
+ fun headers(headers: JsonField) = apply { this.headers = headers }
+
+ 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 [Vertex].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .location()
+ * .project()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
+ fun build(): Vertex =
+ Vertex(
+ checkRequired("location", location),
+ checkRequired("project", project),
+ baseUrl,
+ headers,
+ additionalProperties.toMutableMap(),
+ )
+ }
+
+ private var validated: Boolean = false
+
+ /**
+ * Validates that the types of all values in this object match their expected types
+ * recursively.
+ *
+ * This method is _not_ forwards compatible with new types from the API for existing
+ * fields.
+ *
+ * @throws StagehandInvalidDataException if any value type in this object doesn't
+ * match its expected type.
+ */
+ fun validate(): Vertex = apply {
+ if (validated) {
+ return@apply
+ }
+
+ location()
+ project()
+ baseUrl()
+ headers().ifPresent { it.validate() }
+ 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 (location.asKnown().isPresent) 1 else 0) +
+ (if (project.asKnown().isPresent) 1 else 0) +
+ (if (baseUrl.asKnown().isPresent) 1 else 0) +
+ (headers.asKnown().getOrNull()?.validity() ?: 0)
+
+ /** Custom headers sent with every request to the Vertex AI provider */
+ class Headers
+ @JsonCreator
+ private constructor(
+ @com.fasterxml.jackson.annotation.JsonValue
+ private val additionalProperties: Map
+ ) {
+
+ @JsonAnyGetter
+ @ExcludeMissing
+ fun _additionalProperties(): Map = additionalProperties
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ /** Returns a mutable builder for constructing an instance of [Headers]. */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Headers]. */
+ class Builder internal constructor() {
+
+ private var additionalProperties: MutableMap =
+ mutableMapOf()
+
+ @JvmSynthetic
+ internal fun from(headers: Headers) = apply {
+ additionalProperties = headers.additionalProperties.toMutableMap()
+ }
+
+ fun additionalProperties(additionalProperties: Map