Skip to content

Commit b6bdf1a

Browse files
feat(api): manual updates
1 parent 9b6860a commit b6bdf1a

File tree

24 files changed

+3441
-328
lines changed

24 files changed

+3441
-328
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 7
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-e96507dd78e76fccc77ba7fb09704da127ead6f4d73ea854e9b2150e90787ff4.yml
3-
openapi_spec_hash: 0c2548b8fdd6de6789b19123e69609c1
4-
config_hash: c3abb41dbe698d59b3bf12f393013d54
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-f7d6b6489159f611a2bfdc267ce0a6fc0455bed1ffa0c310044baaa5d8381b9b.yml
3+
openapi_spec_hash: cd88d8068abfde8382da0bed674e440c
4+
config_hash: 5c69fb596588b8ace08203858518c149

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ The SDK throws custom unchecked exception types:
236236
| 5xx | [`InternalServerException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/InternalServerException.kt) |
237237
| others | [`UnexpectedStatusCodeException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/UnexpectedStatusCodeException.kt) |
238238

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

241243
- [`StagehandRetryableException`](stagehand-java-core/src/main/kotlin/com/browserbase/api/errors/StagehandRetryableException.kt): Generic error indicating a failure that could be retried by the client.

stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClient.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.browserbase.api.client.StagehandClientImpl
77
import com.browserbase.api.core.ClientOptions
88
import com.browserbase.api.core.Sleeper
99
import com.browserbase.api.core.Timeout
10+
import com.browserbase.api.core.http.AsyncStreamResponse
1011
import com.browserbase.api.core.http.Headers
1112
import com.browserbase.api.core.http.HttpClient
1213
import com.browserbase.api.core.http.QueryParams
@@ -16,6 +17,7 @@ import java.net.Proxy
1617
import java.time.Clock
1718
import java.time.Duration
1819
import java.util.Optional
20+
import java.util.concurrent.Executor
1921
import javax.net.ssl.HostnameVerifier
2022
import javax.net.ssl.SSLSocketFactory
2123
import javax.net.ssl.X509TrustManager
@@ -121,6 +123,17 @@ class StagehandOkHttpClient private constructor() {
121123
*/
122124
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
123125

126+
/**
127+
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
128+
*
129+
* Defaults to a dedicated cached thread pool.
130+
*
131+
* This class takes ownership of the executor and shuts it down, if possible, when closed.
132+
*/
133+
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
134+
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
135+
}
136+
124137
/**
125138
* The interface to use for delaying execution, like during retries.
126139
*

stagehand-java-client-okhttp/src/main/kotlin/com/browserbase/api/client/okhttp/StagehandOkHttpClientAsync.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.browserbase.api.client.StagehandClientAsyncImpl
77
import com.browserbase.api.core.ClientOptions
88
import com.browserbase.api.core.Sleeper
99
import com.browserbase.api.core.Timeout
10+
import com.browserbase.api.core.http.AsyncStreamResponse
1011
import com.browserbase.api.core.http.Headers
1112
import com.browserbase.api.core.http.HttpClient
1213
import com.browserbase.api.core.http.QueryParams
@@ -16,6 +17,7 @@ import java.net.Proxy
1617
import java.time.Clock
1718
import java.time.Duration
1819
import java.util.Optional
20+
import java.util.concurrent.Executor
1921
import javax.net.ssl.HostnameVerifier
2022
import javax.net.ssl.SSLSocketFactory
2123
import javax.net.ssl.X509TrustManager
@@ -121,6 +123,17 @@ class StagehandOkHttpClientAsync private constructor() {
121123
*/
122124
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
123125

126+
/**
127+
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
128+
*
129+
* Defaults to a dedicated cached thread pool.
130+
*
131+
* This class takes ownership of the executor and shuts it down, if possible, when closed.
132+
*/
133+
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
134+
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
135+
}
136+
124137
/**
125138
* The interface to use for delaying execution, like during retries.
126139
*

stagehand-java-core/src/main/kotlin/com/browserbase/api/core/ClientOptions.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package com.browserbase.api.core
44

5+
import com.browserbase.api.core.http.AsyncStreamResponse
56
import com.browserbase.api.core.http.Headers
67
import com.browserbase.api.core.http.HttpClient
78
import com.browserbase.api.core.http.PhantomReachableClosingHttpClient
@@ -11,6 +12,11 @@ import com.fasterxml.jackson.databind.json.JsonMapper
1112
import java.time.Clock
1213
import java.time.Duration
1314
import java.util.Optional
15+
import java.util.concurrent.Executor
16+
import java.util.concurrent.ExecutorService
17+
import java.util.concurrent.Executors
18+
import java.util.concurrent.ThreadFactory
19+
import java.util.concurrent.atomic.AtomicLong
1420
import kotlin.jvm.optionals.getOrNull
1521

1622
/** A class representing the SDK client configuration. */
@@ -40,6 +46,14 @@ private constructor(
4046
* rarely needs to be overridden.
4147
*/
4248
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
49+
/**
50+
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
51+
*
52+
* Defaults to a dedicated cached thread pool.
53+
*
54+
* This class takes ownership of the executor and shuts it down, if possible, when closed.
55+
*/
56+
@get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
4357
/**
4458
* The interface to use for delaying execution, like during retries.
4559
*
@@ -147,6 +161,7 @@ private constructor(
147161
private var httpClient: HttpClient? = null
148162
private var checkJacksonVersionCompatibility: Boolean = true
149163
private var jsonMapper: JsonMapper = jsonMapper()
164+
private var streamHandlerExecutor: Executor? = null
150165
private var sleeper: Sleeper? = null
151166
private var clock: Clock = Clock.systemUTC()
152167
private var baseUrl: String? = null
@@ -164,6 +179,7 @@ private constructor(
164179
httpClient = clientOptions.originalHttpClient
165180
checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
166181
jsonMapper = clientOptions.jsonMapper
182+
streamHandlerExecutor = clientOptions.streamHandlerExecutor
167183
sleeper = clientOptions.sleeper
168184
clock = clientOptions.clock
169185
baseUrl = clientOptions.baseUrl
@@ -207,6 +223,20 @@ private constructor(
207223
*/
208224
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
209225

226+
/**
227+
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
228+
*
229+
* Defaults to a dedicated cached thread pool.
230+
*
231+
* This class takes ownership of the executor and shuts it down, if possible, when closed.
232+
*/
233+
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
234+
this.streamHandlerExecutor =
235+
if (streamHandlerExecutor is ExecutorService)
236+
PhantomReachableExecutorService(streamHandlerExecutor)
237+
else streamHandlerExecutor
238+
}
239+
210240
/**
211241
* The interface to use for delaying execution, like during retries.
212242
*
@@ -422,6 +452,24 @@ private constructor(
422452
*/
423453
fun build(): ClientOptions {
424454
val httpClient = checkRequired("httpClient", httpClient)
455+
val streamHandlerExecutor =
456+
streamHandlerExecutor
457+
?: PhantomReachableExecutorService(
458+
Executors.newCachedThreadPool(
459+
object : ThreadFactory {
460+
461+
private val threadFactory: ThreadFactory =
462+
Executors.defaultThreadFactory()
463+
private val count = AtomicLong(0)
464+
465+
override fun newThread(runnable: Runnable): Thread =
466+
threadFactory.newThread(runnable).also {
467+
it.name =
468+
"stagehand-stream-handler-thread-${count.getAndIncrement()}"
469+
}
470+
}
471+
)
472+
)
425473
val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper())
426474
val browserbaseApiKey = checkRequired("browserbaseApiKey", browserbaseApiKey)
427475
val browserbaseProjectId = checkRequired("browserbaseProjectId", browserbaseProjectId)
@@ -464,6 +512,7 @@ private constructor(
464512
.build(),
465513
checkJacksonVersionCompatibility,
466514
jsonMapper,
515+
streamHandlerExecutor,
467516
sleeper,
468517
clock,
469518
baseUrl,
@@ -491,6 +540,7 @@ private constructor(
491540
*/
492541
fun close() {
493542
httpClient.close()
543+
(streamHandlerExecutor as? ExecutorService)?.shutdown()
494544
sleeper.close()
495545
}
496546
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// File generated from our OpenAPI spec by Stainless.
2+
3+
@file:JvmName("SseHandler")
4+
5+
package com.browserbase.api.core.handlers
6+
7+
import com.browserbase.api.core.JsonMissing
8+
import com.browserbase.api.core.http.HttpResponse
9+
import com.browserbase.api.core.http.HttpResponse.Handler
10+
import com.browserbase.api.core.http.SseMessage
11+
import com.browserbase.api.core.http.StreamResponse
12+
import com.browserbase.api.core.http.map
13+
import com.browserbase.api.errors.SseException
14+
import com.fasterxml.jackson.databind.json.JsonMapper
15+
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
16+
17+
@JvmSynthetic
18+
internal fun sseHandler(jsonMapper: JsonMapper): Handler<StreamResponse<SseMessage>> =
19+
streamHandler { response, lines ->
20+
val state = SseState(jsonMapper)
21+
var done = false
22+
for (line in lines) {
23+
// Stop emitting messages, but iterate through the full stream.
24+
if (done) {
25+
continue
26+
}
27+
28+
val message = state.decode(line) ?: continue
29+
30+
when {
31+
message.data.startsWith("finished") -> {
32+
// In this case we don't break because we still want to iterate through the full
33+
// stream.
34+
done = true
35+
continue
36+
}
37+
message.data.startsWith("error") -> {
38+
throw SseException.builder()
39+
.statusCode(response.statusCode())
40+
.headers(response.headers())
41+
.body(
42+
try {
43+
jsonMapper.readValue(message.data, jacksonTypeRef())
44+
} catch (e: Exception) {
45+
JsonMissing.of()
46+
}
47+
)
48+
.build()
49+
}
50+
}
51+
52+
if (message.event == null) {
53+
yield(message)
54+
}
55+
}
56+
}
57+
58+
private class SseState(
59+
val jsonMapper: JsonMapper,
60+
var event: String? = null,
61+
val data: MutableList<String> = mutableListOf(),
62+
var lastId: String? = null,
63+
var retry: Int? = null,
64+
) {
65+
// https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
66+
fun decode(line: String): SseMessage? {
67+
if (line.isEmpty()) {
68+
return flush()
69+
}
70+
71+
if (line.startsWith(':')) {
72+
return null
73+
}
74+
75+
val fieldName: String
76+
var value: String
77+
78+
val colonIndex = line.indexOf(':')
79+
if (colonIndex == -1) {
80+
fieldName = line
81+
value = ""
82+
} else {
83+
fieldName = line.substring(0, colonIndex)
84+
value = line.substring(colonIndex + 1)
85+
}
86+
87+
if (value.startsWith(' ')) {
88+
value = value.substring(1)
89+
}
90+
91+
when (fieldName) {
92+
"event" -> event = value
93+
"data" -> data.add(value)
94+
"id" -> {
95+
if (!value.contains('\u0000')) {
96+
lastId = value
97+
}
98+
}
99+
"retry" -> value.toIntOrNull()?.let { retry = it }
100+
}
101+
102+
return null
103+
}
104+
105+
private fun flush(): SseMessage? {
106+
if (isEmpty()) {
107+
return null
108+
}
109+
110+
val message =
111+
SseMessage.builder()
112+
.jsonMapper(jsonMapper)
113+
.event(event)
114+
.data(data.joinToString("\n"))
115+
.id(lastId)
116+
.retry(retry)
117+
.build()
118+
119+
// NOTE: Per the SSE spec, do not reset lastId.
120+
event = null
121+
data.clear()
122+
retry = null
123+
124+
return message
125+
}
126+
127+
private fun isEmpty(): Boolean =
128+
event.isNullOrEmpty() && data.isEmpty() && lastId.isNullOrEmpty() && retry == null
129+
}
130+
131+
@JvmSynthetic
132+
internal inline fun <reified T> Handler<StreamResponse<SseMessage>>.mapJson():
133+
Handler<StreamResponse<T>> =
134+
object : Handler<StreamResponse<T>> {
135+
override fun handle(response: HttpResponse): StreamResponse<T> =
136+
this@mapJson.handle(response).map { it.json<T>() }
137+
}

0 commit comments

Comments
 (0)