From 3f8a44f40f355bfe6d6c738348e295d747096301 Mon Sep 17 00:00:00 2001 From: Mark Stuart Date: Fri, 16 Oct 2026 08:25:24 +0000 Subject: [PATCH] refactor: reuse Java WebSocket connection helpers Castiron-Internal-PR: https://github.com/openai/openai-java-internal/pull/287 Castiron-Source-SHA: ac314daf9f2e48d609d245501665fb466d63d3e1 Castiron-Public-Base-SHA: 812a4fb74885f69b130c82d024f44541f73b8f5e --- .castiron.stats.yml | 10 +- .../openai/gradle/CoreCompilationShards.kt | 2 + .../core/http/AsyncResponseConnection.kt | 7 +- .../com/openai/core/http/WebSocketBinding.kt | 11 + .../openai/core/http/WebSocketConnection.kt | 288 ++++ .../openai/core/http/WebSocketConnections.kt | 98 ++ .../com/openai/core/http/WebSocketOptions.kt | 59 + .../com/openai/helpers/LiveConnection.kt | 400 +----- .../com/openai/helpers/LiveForkConnection.kt | 2 +- .../openai/helpers/LiveSidebandConnection.kt | 2 +- .../com/openai/helpers/RealtimeConnection.kt | 8 +- .../openai/helpers/TranslationConnection.kt | 12 +- .../certificates/CertificateActivateParams.kt | 5 +- .../CertificateDeactivateParams.kt | 5 +- .../invites/InviteCreateParams.kt | 5 +- .../certificates/CertificateActivateParams.kt | 5 +- .../CertificateDeactivateParams.kt | 5 +- .../ModelPermissionUpdateParams.kt | 5 +- .../projects/roles/RoleCreateParams.kt | 5 +- .../projects/roles/RoleUpdateParams.kt | 5 +- .../apikeys/ApiKeyCreateParams.kt | 2 +- .../organization/roles/RoleCreateParams.kt | 5 +- .../organization/roles/RoleUpdateParams.kt | 5 +- .../models/beta/agents/AgentCreateParams.kt | 2 +- .../models/beta/agents/AgentUpdateParams.kt | 2 +- .../environments/EnvironmentCreateParams.kt | 5 +- .../templates/TemplateCreateParams.kt | 17 +- .../templates/TemplateUpdateParams.kt | 17 +- .../agents/sessions/SessionCreateParams.kt | 5 +- .../sessions/events/EventCreateParams.kt | 2 +- .../beta/assistants/AssistantCreateParams.kt | 2 +- .../beta/assistants/AssistantUpdateParams.kt | 2 +- .../beta/responses/ResponseCreateParams.kt | 13 +- .../inputtokens/InputTokenCountParams.kt | 2 +- .../beta/threads/ThreadCreateAndRunParams.kt | 2 +- .../models/beta/threads/ThreadCreateParams.kt | 5 +- .../threads/messages/MessageCreateParams.kt | 5 +- .../beta/threads/runs/RunCreateParams.kt | 8 +- .../runs/RunSubmitToolOutputsParams.kt | 5 +- .../completions/ChatCompletionCreateParams.kt | 17 +- .../containers/ContainerCreateParams.kt | 4 +- .../conversations/ConversationCreateParams.kt | 2 +- .../conversations/items/ItemCreateParams.kt | 2 +- .../models/decisions/DecisionCreateParams.kt | 5 +- .../openai/models/evals/EvalCreateParams.kt | 6 +- .../permissions/PermissionCreateParams.kt | 5 +- .../models/finetuning/jobs/JobCreateParams.kt | 5 +- .../models/live/LiveWebSocketBinding.kt | 18 + .../models/live/forks/ForkWebSocketBinding.kt | 22 + .../live/sideband/SidebandClientEvent.kt | 724 ++++++++++ .../live/sideband/SidebandServerEvent.kt | 1123 +++++++++++++++ .../live/sideband/SidebandWebSocketBinding.kt | 22 + .../RealtimeTranslationServerEvent.kt | 20 +- .../realtime/RealtimeWebSocketBinding.kt | 21 + .../TranslationWebSocketBinding.kt | 21 + .../models/responses/ResponseCreateParams.kt | 13 +- .../responses/ResponseWebSocketBinding.kt | 21 + .../inputtokens/InputTokenCountParams.kt | 2 +- .../models/uploads/UploadCompleteParams.kt | 2 +- .../vectorstores/VectorStoreCreateParams.kt | 2 +- .../filebatches/FileBatchCreateParams.kt | 4 +- .../models/webhooks/WebhookCreateParams.kt | 5 +- .../models/webhooks/WebhookUpdateParams.kt | 5 +- .../live/sideband/SidebandClientEventTest.kt | 590 ++++++++ .../live/sideband/SidebandServerEventTest.kt | 1274 +++++++++++++++++ scripts/castiron/custom_code_budget.py | 20 +- scripts/castiron/generated-files.json | 182 ++- 67 files changed, 4637 insertions(+), 545 deletions(-) create mode 100644 openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnection.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnections.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketOptions.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/LiveWebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkWebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandClientEvent.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandServerEvent.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandWebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeWebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/TranslationWebSocketBinding.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseWebSocketBinding.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandClientEventTest.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandServerEventTest.kt diff --git a/.castiron.stats.yml b/.castiron.stats.yml index bc4f7ffe0..d04f294a7 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: b357eb8c-059b-4c0e-b1c1-2a7c0137a74c +generation_id: b935ba17-9980-45bd-b8c7-1939d1b03aba openapi_spec_hash: e81fa0fd2133715dabef9f00a42557a9 openapi_transformed_spec_hash: bea7a30d23deed3fc8f1c6413a38d354 -config_hash: 77036ac910eb540010c265da4ea569fa -codegen_sha: 71525a371d60d4542ab1d1671382cefb12006578 -codegen_hash: 4d2f67859a910ff993829df42fecc3a024ef29c842916df092cac673c84a6f54 -public_codegen_sha: 85d60517cb25cb1804c571769ef4ccb8d220b7c9 +config_hash: dda57991526f80e9f99d8f9621460eaa +codegen_sha: 9306aff2af8ad922f591ebbc1d29362cf9271bc8 +codegen_hash: bda6b5fe61d5a094103b7cbc38597c82109f4c14bb23d4501d6c3e1569f75878 +public_codegen_sha: 43d374297ff620990fa45fdd39a32079c3905e30 diff --git a/buildSrc/src/main/kotlin/com/openai/gradle/CoreCompilationShards.kt b/buildSrc/src/main/kotlin/com/openai/gradle/CoreCompilationShards.kt index 737b1d629..2c6ed2676 100644 --- a/buildSrc/src/main/kotlin/com/openai/gradle/CoreCompilationShards.kt +++ b/buildSrc/src/main/kotlin/com/openai/gradle/CoreCompilationShards.kt @@ -122,6 +122,8 @@ object CoreCompilationShards { "com/openai/core/http/ResponseWebSocketOptions.kt", "com/openai/core/http/ResponseWebSocketException.kt", "com/openai/core/http/WebSocketClient.kt", + "com/openai/core/http/WebSocketConnection.kt", + "com/openai/core/http/WebSocketConnections.kt", "com/openai/core/http/AuthenticatingHttpClient.kt", "com/openai/core/http/HttpClient.kt", "com/openai/core/http/LoggingHttpClient.kt", diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/AsyncResponseConnection.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/AsyncResponseConnection.kt index 06b903f7b..fe5b0674a 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/http/AsyncResponseConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/AsyncResponseConnection.kt @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode import com.openai.azure.AzureUrlCategory import com.openai.core.ClientOptions import com.openai.core.RequestOptions -import com.openai.core.SecurityOptions import com.openai.core.prepare import com.openai.models.responses.Response import com.openai.models.responses.ResponsesClientEvent @@ -142,12 +141,14 @@ private constructor( HttpRequest.builder() .method(HttpMethod.GET) .baseUrl(clientOptions.baseUrl()) - .addPathSegment("responses") + .pathSegments( + com.openai.models.responses.ResponseWebSocketBinding.create().pathSegments + ) .build() .prepare( clientOptions, options, - SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.responses.ResponseWebSocketBinding.create().security, ) val opening = clientOptions.connectWebSocket( diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketBinding.kt new file mode 100644 index 000000000..1c4c2a4b1 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketBinding.kt @@ -0,0 +1,11 @@ +package com.openai.core.http + +import com.openai.core.SecurityOptions + +/** Connects generated endpoint and event metadata to the SDK-owned connection implementation. */ +internal class WebSocketBinding( + val pathSegments: List, + val security: SecurityOptions, + val eventType: Class, + val validate: (S) -> Unit, +) {} diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnection.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnection.kt new file mode 100644 index 000000000..8172a6083 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnection.kt @@ -0,0 +1,288 @@ +package com.openai.core.http + +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.databind.DeserializationFeature +import com.openai.core.ClientOptions +import com.openai.core.RequestOptions +import java.io.IOException +import java.util.ArrayDeque +import java.util.concurrent.CancellationException +import java.util.concurrent.CompletableFuture + +/** SDK-owned ordered event connection used by generated bindings and API helpers. */ +class WebSocketConnection +internal constructor( + private val clientOptions: ClientOptions, + private val requestOptions: RequestOptions, + private val requestFactory: () -> HttpRequest, + eventType: Class, + private val validate: (S) -> Unit, + private val maxMessageBytes: Int, + private val maxQueuedBytes: Long, + private val maxQueuedEvents: Int, + private val api: String, +) : AutoCloseable { + private val lock = Any() + private val reader = + clientOptions.jsonMapper + .readerFor(eventType) + .with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + + private class Item(val event: S, val bytes: Long) + + private val queue = ArrayDeque>() + private var queuedBytes = 0L + private var socket: WebSocketClient.Connection? = null + private var opening: CompletableFuture? = null + private var waiter: WebSocketReadFuture? = null + private var failure: Throwable? = null + private var closed = false + private var sending = false + + internal fun open( + blocking: Boolean, + create: (WebSocketConnection) -> T, + ): CompletableFuture { + val connection = this + val result = CompletableFuture() + result.whenComplete { _, _ -> if (result.isCancelled) connection.close() } + try { + clientOptions.requireWebSocketTransport() + val request = requestFactory() + val pending = + clientOptions.connectWebSocket( + request, + requestOptions, + maxMessageBytes, + object : WebSocketClient.Listener { + override fun onMessage(text: String) = connection.accept(text) + + override fun onClosed(code: Int) = + connection.fail(IOException("$api WebSocket closed (code $code)")) + + override fun onFailure(error: Throwable) = connection.fail(error) + }, + ) + val shouldCancel = + synchronized(connection.lock) { + if (connection.closed || connection.failure != null) true + else { + connection.opening = pending + false + } + } + if (shouldCancel) pending.cancel(true) + pending.whenComplete { active, error -> + val problem = + synchronized(connection.lock) { + connection.opening = null + if (error != null) error + else if (connection.closed || connection.failure != null) + connection.failure ?: CancellationException("$api connection closed") + else { + connection.socket = active + null + } + } + val complete: () -> Unit = { + // The owner or socket can close while completion waits on the executor. + // Inspect state here, but keep user callbacks outside the lock. + val completionError = + problem + ?: synchronized(connection.lock) { + when { + connection.failure != null -> connection.failure + connection.closed -> + CancellationException("$api connection closed") + clientOptions.isWebSocketClosed() -> + CancellationException("Client is closed") + else -> null + } + } + if (completionError != null) { + connection.fail(completionError) + result.completeExceptionally(completionError) + } else if (!result.complete(create(connection))) connection.close() + } + if (problem != null) { + connection.fail(problem) + // The handshake produced a socket after cancellation or an early + // listener failure. It was never stored in connection.socket. + // Retire it off the completing/transport thread and settle opening even + // if the transport's close throws. + CompletableFuture.runAsync { + try { + active?.close() + } catch (closing: Throwable) { + if (closing !== problem) problem.addSuppressed(closing) + } finally { + if (blocking) complete() else connection.dispatch(complete) + } + } + } else { + // A synchronous open uses an unexposed future with no user callbacks. + // It must not queue completion behind its blocking caller's executor. + if (blocking) complete() else connection.dispatch(complete) + } + } + } catch (error: Exception) { + connection.fail(error) + result.completeExceptionally(error) + } + return result + } + + private fun await(future: CompletableFuture): T = awaitWebSocketFuture(future, api) + + /** Returns one event, even if the peer closes immediately after it. */ + fun receive(): S = await(receiveFuture(blocking = true)) + + /** Canceling an unassigned read leaves the session open for the next read. */ + fun receiveAsync(): CompletableFuture = receiveFuture(blocking = false) + + private fun receiveFuture(blocking: Boolean): CompletableFuture { + synchronized(lock) { + check(!closed) { "$api connection is closed" } + if (waiter?.let { it.isDone || it.cancellationReserved } == true) waiter = null + check(waiter == null) { "A $api receive is already pending" } + if (queue.isNotEmpty()) { + val item = queue.removeFirst() + queuedBytes -= item.bytes + return CompletableFuture.completedFuture(item.event) + } + failure?.let { + return CompletableFuture().apply { completeExceptionally(it) } + } + return WebSocketReadFuture(lock, blocking).also { next -> + waiter = next + next.whenComplete { _, _ -> + synchronized(lock) { if (waiter === next) waiter = null } + } + } + } + } + + /** + * Successful send means accepted by the transport, not acknowledged by the server. A failed + * write makes the connection unusable because the remote outcome is uncertain. The caller can + * explicitly open a fresh connection; no commands are replayed. + */ + fun send(event: C) = sendChecked(event) {} + + internal fun sendChecked(event: C, checkCommand: (String) -> Unit) { + synchronized(lock) { + check(!closed && failure == null && socket != null) { "$api is not connected" } + check(!sending) { "A $api send is already pending; command was not sent" } + sending = true + } + try { + val text = clientOptions.jsonMapper.writeValueAsString(event) + checkCommand(text) + require(utf8Size(text) <= maxMessageBytes) { "$api command exceeds maxMessageBytes" } + val active = + synchronized(lock) { + check(!closed && failure == null) { "$api is not connected" } + socket ?: throw IllegalStateException("$api is not connected") + } + try { + active.send(text) + } catch (error: Throwable) { + if (error !is WebSocketWriteNotAttempted) fail(error) + throw error + } + } finally { + synchronized(lock) { sending = false } + } + } + + override fun close() { + synchronized(lock) { + if (closed) return + closed = true + queue.clear() + queuedBytes = 0 + } + fail(CancellationException("$api connection closed")) + } + + private fun accept(text: String) { + try { + // Reject malformed envelopes independently of lenient or user-supplied model readers. + clientOptions.jsonMapper.factory.createParser(text).use { parser -> + require(parser.nextToken() == JsonToken.START_OBJECT) { "Invalid $api event" } + var validType = false + while (parser.nextToken() == JsonToken.FIELD_NAME) { + val name = parser.currentName + val token = parser.nextToken() + if (name == "type") validType = token == JsonToken.VALUE_STRING + parser.skipChildren() + } + require( + parser.currentToken() == JsonToken.END_OBJECT && + validType && + parser.nextToken() == null + ) { + "Invalid $api event" + } + } + val event: S = reader.readValue(text) + if (requestOptions.responseValidation ?: clientOptions.responseValidation) + validate(event) + val bytes = utf8Size(text) + val target = + synchronized(lock) { + if (closed || failure != null) return + val available = + waiter?.takeUnless { it.claimed || it.cancellationReserved || it.isDone } + if (available == null) { + if (queue.size >= maxQueuedEvents || bytes > maxQueuedBytes - queuedBytes) + throw IOException("$api event buffer is full") + queue.addLast(Item(event, bytes)) + queuedBytes += bytes + } else available.claimed = true + available + } + if (target != null) { + // Only our private blocking read may be completed directly. It cannot expose + // user callbacks on the physical reader or compete for the caller's executor. + if (target.blocking) target.complete(event) else dispatch { target.complete(event) } + } + } catch (error: Exception) { + fail(error) + } + } + + private fun fail(error: Throwable) { + val detached = + synchronized(lock) { + if (failure != null) return + failure = error + val unassigned = + waiter?.takeUnless { it.claimed || it.cancellationReserved || it.isDone } + waiter = null + Triple(socket, opening, unassigned).also { + socket = null + opening = null + } + } + // Listener callbacks can run on a custom transport's dedicated reader, and its + // close() may join that reader. Never cancel an opening or close a socket on that thread. + CompletableFuture.runAsync { + try { + detached.second?.cancel(true) + detached.first?.close() + } catch (closing: Throwable) { + if (closing !== error) error.addSuppressed(closing) + } finally { + detached.third?.let { read -> + if (read.blocking) read.completeExceptionally(error) + else dispatch { read.completeExceptionally(error) } + } + } + } + } + + // User completion callbacks never run on the transport reader, including with a direct + // executor. + private fun dispatch(action: () -> Unit) = clientOptions.dispatchWebSocketCallback(action) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnections.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnections.kt new file mode 100644 index 000000000..eb5c9b378 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketConnections.kt @@ -0,0 +1,98 @@ +package com.openai.core.http + +import com.openai.azure.AzureUrlCategory +import com.openai.core.ClientOptions +import com.openai.core.RequestOptions +import com.openai.core.prepare +import java.net.URI +import java.net.URLDecoder +import java.net.URLEncoder +import java.util.concurrent.CompletableFuture + +/** Default request policy for APIs that opt into the generic connection. */ +internal object WebSocketConnections { + fun connect( + binding: WebSocketBinding, + clientOptions: ClientOptions, + options: WebSocketOptions, + requestOptions: RequestOptions, + ): WebSocketConnection = + awaitWebSocketFuture( + open(binding, clientOptions, options, requestOptions, true), + "WebSocket", + ) + + fun connectAsync( + binding: WebSocketBinding, + clientOptions: ClientOptions, + options: WebSocketOptions, + requestOptions: RequestOptions, + ): CompletableFuture> = + open(binding, clientOptions, options, requestOptions, false) + + private fun open( + binding: WebSocketBinding, + clientOptions: ClientOptions, + options: WebSocketOptions, + requestOptions: RequestOptions, + blocking: Boolean, + ): CompletableFuture> = + WebSocketConnection( + clientOptions, + requestOptions, + { request(binding, clientOptions, options) }, + binding.eventType, + binding.validate, + options.maxMessageBytes, + options.maxQueuedBytes, + options.maxQueuedEvents, + "WebSocket", + ) + .open(blocking) { it } + + private fun request( + binding: WebSocketBinding<*, *>, + clientOptions: ClientOptions, + options: WebSocketOptions, + ): HttpRequest { + val baseUrl = URI(clientOptions.baseUrl()) + val requestBase = baseUrl.toASCIIString().substringBefore('#').substringBefore('?') + require( + AzureUrlCategory.categorizeBaseUrl(requestBase, clientOptions.azureUrlPathMode) != + AzureUrlCategory.AZURE_LEGACY + ) { + "WebSockets require unified Azure routing" + } + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(requestBase) + .pathSegments(binding.pathSegments) + .apply { + baseUrl.rawQuery + ?.split('&') + ?.filter { it.isNotEmpty() } + ?.forEach { + putQueryParam( + URLDecoder.decode(it.substringBefore('='), "UTF-8"), + URLDecoder.decode(it.substringAfter('=', ""), "UTF-8"), + ) + } + } + .build() + .prepare(clientOptions, options, binding.security) + // Both custom transports and OkHttp must see the same once-encoded path. + val separator = if (requestBase.endsWith("/")) "" else "/" + return request + .toBuilder() + .baseUrl( + requestBase + + separator + + binding.pathSegments.joinToString("/") { + URLEncoder.encode(it, "UTF-8").replace("+", "%20") + } + ) + .pathSegments(emptyList()) + .build() + } +} diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketOptions.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketOptions.kt new file mode 100644 index 000000000..f9086bf61 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/WebSocketOptions.kt @@ -0,0 +1,59 @@ +package com.openai.core.http + +import com.openai.core.Params + +/** Per-connection WebSocket options, including optional limits for slow consumers. */ +class WebSocketOptions +private constructor( + @get:JvmName("maxMessageBytes") val maxMessageBytes: Int, + @get:JvmName("maxQueuedBytes") val maxQueuedBytes: Long, + @get:JvmName("maxQueuedEvents") val maxQueuedEvents: Int, + private val headers: Headers, + private val queryParams: QueryParams, +) : Params { + override fun _headers() = headers + + override fun _queryParams() = queryParams + + companion object { + @JvmStatic fun builder() = Builder() + + @JvmStatic fun defaults() = builder().build() + } + + class Builder internal constructor() { + private var maxMessageBytes = Int.MAX_VALUE + private var maxQueuedBytes = Long.MAX_VALUE + private var maxQueuedEvents = Int.MAX_VALUE + private val headers = Headers.builder() + private val queryParams = QueryParams.builder() + + fun putHeader(name: String, value: String) = apply { headers.put(name, value) } + + fun putQueryParam(name: String, value: String) = apply { queryParams.put(name, value) } + + fun maxMessageBytes(value: Int) = apply { + require(value > 0) + maxMessageBytes = value + } + + fun maxQueuedBytes(value: Long) = apply { + require(value > 0) + maxQueuedBytes = value + } + + fun maxQueuedEvents(value: Int) = apply { + require(value > 0) + maxQueuedEvents = value + } + + fun build() = + WebSocketOptions( + maxMessageBytes, + maxQueuedBytes, + maxQueuedEvents, + headers.build(), + queryParams.build(), + ) + } +} diff --git a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveConnection.kt b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveConnection.kt index a84a35f4a..795c019ae 100644 --- a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveConnection.kt @@ -1,29 +1,18 @@ package com.openai.helpers -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.DeserializationFeature import com.google.errorprone.annotations.MustBeClosed import com.openai.azure.AzureUrlCategory import com.openai.core.ClientOptions import com.openai.core.RequestOptions -import com.openai.core.SecurityOptions import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest -import com.openai.core.http.WebSocketClient -import com.openai.core.http.WebSocketReadFuture -import com.openai.core.http.WebSocketWriteNotAttempted import com.openai.core.http.awaitWebSocketFuture -import com.openai.core.http.dispatchWebSocketCallback -import com.openai.core.http.utf8Size import com.openai.core.prepare import com.openai.models.live.ClientEvent import com.openai.models.live.ServerEvent -import java.io.IOException import java.net.URI import java.net.URLDecoder import java.net.URLEncoder -import java.util.ArrayDeque -import java.util.concurrent.CancellationException import java.util.concurrent.CompletableFuture /** @@ -84,7 +73,7 @@ class LiveConnection private constructor(private val socket: LiveSocket( private val clientOptions: ClientOptions, private val options: LiveWebSocketOptions, - private val requestOptions: RequestOptions, - private val path: List, + requestOptions: RequestOptions, + private val binding: com.openai.core.http.WebSocketBinding<*, *>, eventType: Class, - private val validate: (S) -> Unit, + validate: (S) -> Unit, private val embedEncodedPath: Boolean = false, ) : AutoCloseable { - private val lock = Any() - private val reader = - clientOptions.jsonMapper - .readerFor(eventType) - .with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) - - private class Item(val event: S, val bytes: Long) - - private val queue = ArrayDeque>() - private var queuedBytes = 0L - private var socket: WebSocketClient.Connection? = null - private var opening: CompletableFuture? = null - private var waiter: WebSocketReadFuture? = null - private var failure: Throwable? = null - private var closed = false - private var sending = false + private val path = binding.pathSegments + + private val connection = + com.openai.core.http.WebSocketConnection( + clientOptions, + requestOptions, + ::request, + eventType, + validate, + options.maxMessageBytes, + options.maxQueuedBytes, + options.maxQueuedEvents, + "Live", + ) fun open( blocking: Boolean, create: (LiveSocket) -> T, - ): CompletableFuture { - val connection = this - val result = CompletableFuture() - result.whenComplete { _, _ -> if (result.isCancelled) connection.close() } - try { - clientOptions.requireWebSocketTransport() - val baseUrl = URI(clientOptions.baseUrl()) - val requestBase = baseUrl.toASCIIString().substringBefore('#').substringBefore('?') - require( - AzureUrlCategory.categorizeBaseUrl(requestBase, clientOptions.azureUrlPathMode) != - AzureUrlCategory.AZURE_LEGACY - ) { - "Live WebSockets require unified Azure routing" - } - // HttpRequest.url() appends segments to baseUrl. Canonicalize before both - // authentication and transport so custom WebSocketClient implementations see - // the same URL that OkHttp sends. - val request = - HttpRequest.builder() - .method(HttpMethod.GET) - .baseUrl(requestBase) - .pathSegments(path) - .apply { - baseUrl.rawQuery - ?.split('&') - ?.filter { it.isNotEmpty() } - ?.forEach { - putQueryParam( - URLDecoder.decode(it.substringBefore('='), "UTF-8"), - URLDecoder.decode(it.substringAfter('=', ""), "UTF-8"), - ) - } - } - .build() - .prepare( - clientOptions, - options, - SecurityOptions.builder().bearerAuth(true).build(), - ) - // Live requires unified routing as checked above. Shared request - // preparation can still classify the original query-bearing base URL as - // legacy; retain all prepared auth/defaults and this exact Live endpoint. - .toBuilder() - .pathSegments(path) - .apply { - if (embedEncodedPath) { - // Fork IDs must address the same resource through request.url() and - // OkHttp's addPathSegment. Validate the raw path in prepare() above, - // then encode once as URL path (not form data) before authentication. - // Keep the suffix in the base so neither transport encodes it again. - val separator = if (requestBase.endsWith("/")) "" else "/" - baseUrl( - requestBase + - separator + - path.joinToString("/") { - URLEncoder.encode(it, "UTF-8").replace("+", "%20") - } - ) - pathSegments(emptyList()) - } - } - .build() - val pending = - clientOptions.connectWebSocket( - request, - requestOptions, - options.maxMessageBytes, - object : WebSocketClient.Listener { - override fun onMessage(text: String) = connection.accept(text) + ): CompletableFuture = connection.open(blocking) { create(this) } - override fun onClosed(code: Int) = - connection.fail(IOException("Live WebSocket closed (code $code)")) + fun receive(): S = connection.receive() - override fun onFailure(error: Throwable) = connection.fail(error) - }, - ) - val shouldCancel = - synchronized(connection.lock) { - if (connection.closed || connection.failure != null) true - else { - connection.opening = pending - false - } - } - if (shouldCancel) pending.cancel(true) - pending.whenComplete { active, error -> - val problem = - synchronized(connection.lock) { - connection.opening = null - if (error != null) error - else if (connection.closed || connection.failure != null) - connection.failure ?: CancellationException("Live connection closed") - else { - connection.socket = active - null - } - } - val complete: () -> Unit = { - // The owner or socket can close while completion waits on the executor. - // Inspect state here, but keep user callbacks outside the lock. - val completionError = - problem - ?: synchronized(connection.lock) { - when { - connection.failure != null -> connection.failure - connection.closed -> - CancellationException("Live connection closed") - clientOptions.isWebSocketClosed() -> - CancellationException("Client is closed") - else -> null - } - } - if (completionError != null) { - connection.fail(completionError) - result.completeExceptionally(completionError) - } else if (!result.complete(create(connection))) connection.close() - } - if (problem != null) { - connection.fail(problem) - // The handshake produced a socket after cancellation or an early - // listener failure. It was never stored in connection.socket. - // Retire it off the completing/transport thread and settle opening even - // if the transport's close throws. - CompletableFuture.runAsync { - try { - active?.close() - } catch (closing: Throwable) { - if (closing !== problem) problem.addSuppressed(closing) - } finally { - if (blocking) complete() else connection.dispatch(complete) - } - } - } else { - // A synchronous open uses an unexposed future with no user callbacks. - // It must not queue completion behind its blocking caller's executor. - if (blocking) complete() else connection.dispatch(complete) - } - } - } catch (error: Exception) { - connection.fail(error) - result.completeExceptionally(error) - } - return result - } + fun receiveAsync(): CompletableFuture = connection.receiveAsync() - companion object { - fun await(future: CompletableFuture): T = awaitWebSocketFuture(future, "Live") - } - - /** Returns one event, even if the peer closes immediately after it. */ - fun receive(): S = await(receiveFuture(blocking = true)) + fun send(event: C, checkCommand: (String) -> Unit = {}) = + connection.sendChecked(event, checkCommand) - /** Canceling an unassigned read leaves the session open for the next read. */ - fun receiveAsync(): CompletableFuture = receiveFuture(blocking = false) + override fun close() = connection.close() - private fun receiveFuture(blocking: Boolean): CompletableFuture { - synchronized(lock) { - check(!closed) { "Live connection is closed" } - if (waiter?.let { it.isDone || it.cancellationReserved } == true) waiter = null - check(waiter == null) { "A Live receive is already pending" } - if (queue.isNotEmpty()) { - val item = queue.removeFirst() - queuedBytes -= item.bytes - return CompletableFuture.completedFuture(item.event) - } - failure?.let { - return CompletableFuture().apply { completeExceptionally(it) } - } - return WebSocketReadFuture(lock, blocking).also { next -> - waiter = next - next.whenComplete { _, _ -> - synchronized(lock) { if (waiter === next) waiter = null } - } - } - } - } - - /** - * Successful send means accepted by the transport, not acknowledged by the server. A failed - * write makes the connection unusable because the remote outcome is uncertain. The caller can - * explicitly open a fresh connection; no commands are replayed. - */ - fun send(event: C, checkCommand: (String) -> Unit = {}) { - synchronized(lock) { - check(!closed && failure == null && socket != null) { "Live is not connected" } - check(!sending) { "A Live send is already pending; command was not sent" } - sending = true - } - try { - val text = clientOptions.jsonMapper.writeValueAsString(event) - checkCommand(text) - require(utf8Size(text) <= options.maxMessageBytes) { - "Live command exceeds maxMessageBytes" - } - val active = - synchronized(lock) { - check(!closed && failure == null) { "Live is not connected" } - socket ?: throw IllegalStateException("Live is not connected") - } - try { - active.send(text) - } catch (error: Throwable) { - if (error !is WebSocketWriteNotAttempted) fail(error) - throw error - } - } finally { - synchronized(lock) { sending = false } - } + companion object { + fun await(future: CompletableFuture): T = awaitWebSocketFuture(future, "Live") } - override fun close() { - synchronized(lock) { - if (closed) return - closed = true - queue.clear() - queuedBytes = 0 + private fun request(): HttpRequest { + val baseUrl = URI(clientOptions.baseUrl()) + val requestBase = baseUrl.toASCIIString().substringBefore('#').substringBefore('?') + require( + AzureUrlCategory.categorizeBaseUrl(requestBase, clientOptions.azureUrlPathMode) != + AzureUrlCategory.AZURE_LEGACY + ) { + "Live WebSockets require unified Azure routing" } - fail(CancellationException("Live connection closed")) - } - - private fun accept(text: String) { - try { - // Reject malformed envelopes independently of lenient or user-supplied model readers. - clientOptions.jsonMapper.factory.createParser(text).use { parser -> - require(parser.nextToken() == JsonToken.START_OBJECT) { "Invalid Live event" } - var validType = false - while (parser.nextToken() == JsonToken.FIELD_NAME) { - val name = parser.currentName - val token = parser.nextToken() - if (name == "type") validType = token == JsonToken.VALUE_STRING - parser.skipChildren() - } - require( - parser.currentToken() == JsonToken.END_OBJECT && - validType && - parser.nextToken() == null - ) { - "Invalid Live event" + // HttpRequest.url() appends segments to baseUrl. Canonicalize before both + // authentication and transport so custom WebSocketClient implementations see + // the same URL that OkHttp sends. + val request = + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl(requestBase) + .pathSegments(path) + .apply { + baseUrl.rawQuery + ?.split('&') + ?.filter { it.isNotEmpty() } + ?.forEach { + putQueryParam( + URLDecoder.decode(it.substringBefore('='), "UTF-8"), + URLDecoder.decode(it.substringAfter('=', ""), "UTF-8"), + ) + } } - } - val event: S = reader.readValue(text) - if (requestOptions.responseValidation ?: clientOptions.responseValidation) - validate(event) - val bytes = utf8Size(text) - val target = - synchronized(lock) { - if (closed || failure != null) return - val available = - waiter?.takeUnless { it.claimed || it.cancellationReserved || it.isDone } - if (available == null) { - if ( - queue.size >= options.maxQueuedEvents || - bytes > options.maxQueuedBytes - queuedBytes + .build() + .prepare(clientOptions, options, binding.security) + // Live requires unified routing as checked above. Shared request + // preparation can still classify the original query-bearing base URL as + // legacy; retain all prepared auth/defaults and this exact Live endpoint. + .toBuilder() + .pathSegments(path) + .apply { + if (embedEncodedPath) { + // Fork IDs must address the same resource through request.url() and + // OkHttp's addPathSegment. Validate the raw path in prepare() above, + // then encode once as URL path (not form data) before authentication. + // Keep the suffix in the base so neither transport encodes it again. + val separator = if (requestBase.endsWith("/")) "" else "/" + baseUrl( + requestBase + + separator + + path.joinToString("/") { + URLEncoder.encode(it, "UTF-8").replace("+", "%20") + } ) - throw IOException("Live event buffer is full") - queue.addLast(Item(event, bytes)) - queuedBytes += bytes - } else available.claimed = true - available - } - if (target != null) { - // Only our private blocking read may be completed directly. It cannot expose - // user callbacks on the physical reader or compete for the caller's executor. - if (target.blocking) target.complete(event) else dispatch { target.complete(event) } - } - } catch (error: Exception) { - fail(error) - } - } - - private fun fail(error: Throwable) { - val detached = - synchronized(lock) { - if (failure != null) return - failure = error - val unassigned = - waiter?.takeUnless { it.claimed || it.cancellationReserved || it.isDone } - waiter = null - Triple(socket, opening, unassigned).also { - socket = null - opening = null - } - } - // Listener callbacks can run on a custom transport's dedicated reader, and its - // close() may join that reader. Never cancel an opening or close a socket on that thread. - CompletableFuture.runAsync { - try { - detached.second?.cancel(true) - detached.first?.close() - } catch (closing: Throwable) { - if (closing !== error) error.addSuppressed(closing) - } finally { - detached.third?.let { read -> - if (read.blocking) read.completeExceptionally(error) - else dispatch { read.completeExceptionally(error) } + pathSegments(emptyList()) + } } - } - } + .build() + return request } - - // User completion callbacks never run on the transport reader, including with a direct - // executor. - private fun dispatch(action: () -> Unit) = clientOptions.dispatchWebSocketCallback(action) } diff --git a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveForkConnection.kt b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveForkConnection.kt index b66d68853..007b8e2c0 100644 --- a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveForkConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveForkConnection.kt @@ -72,7 +72,7 @@ private constructor( clientOptions, options, requestOptions, - listOf("live", "sessions", storedSessionId, "fork"), + com.openai.models.live.forks.ForkWebSocketBinding.create(storedSessionId), ForkServerEvent::class.java, { it.validate() }, embedEncodedPath = true, diff --git a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveSidebandConnection.kt b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveSidebandConnection.kt index 59c190ae7..1acef56de 100644 --- a/openai-java-core/src/main/kotlin/com/openai/helpers/LiveSidebandConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/helpers/LiveSidebandConnection.kt @@ -76,7 +76,7 @@ private constructor( clientOptions, options, requestOptions, - listOf("live", "sessions", sessionId, "attach"), + com.openai.models.live.sideband.SidebandWebSocketBinding.create(sessionId), ServerEvent::class.java, { it.validate() }, embedEncodedPath = true, diff --git a/openai-java-core/src/main/kotlin/com/openai/helpers/RealtimeConnection.kt b/openai-java-core/src/main/kotlin/com/openai/helpers/RealtimeConnection.kt index 49dd2bec3..8239cf37f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/helpers/RealtimeConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/helpers/RealtimeConnection.kt @@ -6,7 +6,6 @@ import com.google.errorprone.annotations.MustBeClosed import com.openai.azure.AzureUrlCategory import com.openai.core.ClientOptions import com.openai.core.RequestOptions -import com.openai.core.SecurityOptions import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.WebSocketClient @@ -102,12 +101,15 @@ private constructor( HttpRequest.builder() .method(HttpMethod.GET) .baseUrl(clientOptions.baseUrl()) - .addPathSegment("realtime") + .pathSegments( + com.openai.models.realtime.RealtimeWebSocketBinding.create() + .pathSegments + ) .build() .prepare( clientOptions, options, - SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.realtime.RealtimeWebSocketBinding.create().security, ) val pending = clientOptions.connectWebSocket( diff --git a/openai-java-core/src/main/kotlin/com/openai/helpers/TranslationConnection.kt b/openai-java-core/src/main/kotlin/com/openai/helpers/TranslationConnection.kt index 6f740888a..9d94026e8 100644 --- a/openai-java-core/src/main/kotlin/com/openai/helpers/TranslationConnection.kt +++ b/openai-java-core/src/main/kotlin/com/openai/helpers/TranslationConnection.kt @@ -6,7 +6,6 @@ import com.google.errorprone.annotations.MustBeClosed import com.openai.azure.AzureUrlCategory import com.openai.core.ClientOptions import com.openai.core.RequestOptions -import com.openai.core.SecurityOptions import com.openai.core.http.HttpMethod import com.openai.core.http.HttpRequest import com.openai.core.http.WebSocketClient @@ -176,8 +175,11 @@ private constructor( HttpRequest.builder() .method(HttpMethod.GET) .baseUrl(baseUrl.toASCIIString().substringBefore('#').substringBefore('?')) - .addPathSegment("realtime") - .addPathSegment("translations") + .pathSegments( + com.openai.models.realtime.translations.TranslationWebSocketBinding + .create() + .pathSegments + ) .apply { baseQuery .filter { it.isNotEmpty() } @@ -192,7 +194,9 @@ private constructor( .prepare( clientOptions, options, - SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.realtime.translations.TranslationWebSocketBinding + .create() + .security, ) require(request.queryParams.values("intent").isEmpty()) { "Translation does not accept the Realtime intent query parameter" diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateParams.kt index db8437324..be1850f01 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateParams.kt @@ -345,9 +345,8 @@ private constructor( */ fun addCertificateId(certificateId: String) = apply { certificateIds = - (certificateIds ?: JsonField.of(mutableListOf())).also { - checkKnown("certificateIds", it).add(certificateId) - } + (certificateIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("certificateIds", it).add(certificateId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateParams.kt index defb71422..180def17f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateParams.kt @@ -345,9 +345,8 @@ private constructor( */ fun addCertificateId(certificateId: String) = apply { certificateIds = - (certificateIds ?: JsonField.of(mutableListOf())).also { - checkKnown("certificateIds", it).add(certificateId) - } + (certificateIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("certificateIds", it).add(certificateId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteCreateParams.kt index 2fb1408f0..67486fb8a 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteCreateParams.kt @@ -481,9 +481,8 @@ private constructor( */ fun addProject(project: Project) = apply { projects = - (projects ?: JsonField.of(mutableListOf())).also { - checkKnown("projects", it).add(project) - } + (projects?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("projects", it).add(project) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateParams.kt index 755c4004a..388c4a0c5 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateParams.kt @@ -363,9 +363,8 @@ private constructor( */ fun addCertificateId(certificateId: String) = apply { certificateIds = - (certificateIds ?: JsonField.of(mutableListOf())).also { - checkKnown("certificateIds", it).add(certificateId) - } + (certificateIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("certificateIds", it).add(certificateId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateParams.kt index 1e85858e3..a8e40ccec 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateParams.kt @@ -362,9 +362,8 @@ private constructor( */ fun addCertificateId(certificateId: String) = apply { certificateIds = - (certificateIds ?: JsonField.of(mutableListOf())).also { - checkKnown("certificateIds", it).add(certificateId) - } + (certificateIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("certificateIds", it).add(certificateId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ModelPermissionUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ModelPermissionUpdateParams.kt index 5a87d1aa9..23f4a1e40 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ModelPermissionUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ModelPermissionUpdateParams.kt @@ -421,9 +421,8 @@ private constructor( */ fun addModelId(modelId: String) = apply { modelIds = - (modelIds ?: JsonField.of(mutableListOf())).also { - checkKnown("modelIds", it).add(modelId) - } + (modelIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("modelIds", it).add(modelId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleCreateParams.kt index 1ea5d94a4..f960508d5 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleCreateParams.kt @@ -467,9 +467,8 @@ private constructor( */ fun addPermission(permission: String) = apply { permissions = - (permissions ?: JsonField.of(mutableListOf())).also { - checkKnown("permissions", it).add(permission) - } + (permissions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("permissions", it).add(permission) } } /** Unique name for the role. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleUpdateParams.kt index 005fe8583..b8cc0185b 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleUpdateParams.kt @@ -494,9 +494,8 @@ private constructor( */ fun addPermission(permission: String) = apply { permissions = - (permissions ?: JsonField.of(mutableListOf())).also { - checkKnown("permissions", it).add(permission) - } + (permissions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("permissions", it).add(permission) } } /** New name for the role. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateParams.kt index 34aed23bb..d38ab3d60 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateParams.kt @@ -522,7 +522,7 @@ private constructor( */ fun addScope(scope: String) = apply { scopes = - (scopes ?: JsonField.of(mutableListOf())).also { + (scopes?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("scopes", it).add(scope) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleCreateParams.kt index d1f828fed..68cd40f3a 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleCreateParams.kt @@ -446,9 +446,8 @@ private constructor( */ fun addPermission(permission: String) = apply { permissions = - (permissions ?: JsonField.of(mutableListOf())).also { - checkKnown("permissions", it).add(permission) - } + (permissions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("permissions", it).add(permission) } } /** Unique name for the role. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleUpdateParams.kt index 3a502d956..fce4de7c7 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleUpdateParams.kt @@ -472,9 +472,8 @@ private constructor( */ fun addPermission(permission: String) = apply { permissions = - (permissions ?: JsonField.of(mutableListOf())).also { - checkKnown("permissions", it).add(permission) - } + (permissions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("permissions", it).add(permission) } } /** New name for the role. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateParams.kt index c3a8292b7..b034e1438 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateParams.kt @@ -983,7 +983,7 @@ private constructor( */ fun addTool(tool: PersistedAgentToolParam) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentUpdateParams.kt index 67e179fbf..04245f654 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentUpdateParams.kt @@ -978,7 +978,7 @@ private constructor( */ fun addTool(tool: PersistedAgentToolParam) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentCreateParams.kt index b3d356c10..eda204a84 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentCreateParams.kt @@ -442,9 +442,8 @@ private constructor( */ fun addVaultId(vaultId: String) = apply { vaultIds = - (vaultIds ?: JsonField.of(mutableListOf())).also { - checkKnown("vaultIds", it).add(vaultId) - } + (vaultIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("vaultIds", it).add(vaultId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateCreateParams.kt index 887c9b1d2..857c29f24 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateCreateParams.kt @@ -909,9 +909,9 @@ private constructor( */ fun addCapabilityDirectory(capabilityDirectory: String) = apply { capabilityDirectories = - (capabilityDirectories ?: JsonField.of(mutableListOf())).also { - checkKnown("capabilityDirectories", it).add(capabilityDirectory) - } + (capabilityDirectories?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("capabilityDirectories", it).add(capabilityDirectory) } } /** @@ -971,7 +971,7 @@ private constructor( */ fun addFile(file: HostedEnvironmentFileParam) = apply { files = - (files ?: JsonField.of(mutableListOf())).also { + (files?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("files", it).add(file) } } @@ -1056,7 +1056,7 @@ private constructor( */ fun addPlugin(plugin: HostedPluginParam) = apply { plugins = - (plugins ?: JsonField.of(mutableListOf())).also { + (plugins?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("plugins", it).add(plugin) } } @@ -1087,9 +1087,8 @@ private constructor( */ fun addSetupCommand(setupCommand: SetupCommandParam) = apply { setupCommands = - (setupCommands ?: JsonField.of(mutableListOf())).also { - checkKnown("setupCommands", it).add(setupCommand) - } + (setupCommands?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("setupCommands", it).add(setupCommand) } } /** @@ -1119,7 +1118,7 @@ private constructor( */ fun addSkill(skill: HostedSkillParam) = apply { skills = - (skills ?: JsonField.of(mutableListOf())).also { + (skills?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("skills", it).add(skill) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateUpdateParams.kt index 4b4407501..7d7810234 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateUpdateParams.kt @@ -919,9 +919,9 @@ private constructor( */ fun addCapabilityDirectory(capabilityDirectory: String) = apply { capabilityDirectories = - (capabilityDirectories ?: JsonField.of(mutableListOf())).also { - checkKnown("capabilityDirectories", it).add(capabilityDirectory) - } + (capabilityDirectories?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("capabilityDirectories", it).add(capabilityDirectory) } } /** Replacement desktop configuration, or null to disable the desktop. */ @@ -978,7 +978,7 @@ private constructor( */ fun addFile(file: HostedEnvironmentFileParam) = apply { files = - (files ?: JsonField.of(mutableListOf())).also { + (files?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("files", it).add(file) } } @@ -1063,7 +1063,7 @@ private constructor( */ fun addPlugin(plugin: HostedPluginParam) = apply { plugins = - (plugins ?: JsonField.of(mutableListOf())).also { + (plugins?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("plugins", it).add(plugin) } } @@ -1094,9 +1094,8 @@ private constructor( */ fun addSetupCommand(setupCommand: SetupCommandParam) = apply { setupCommands = - (setupCommands ?: JsonField.of(mutableListOf())).also { - checkKnown("setupCommands", it).add(setupCommand) - } + (setupCommands?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("setupCommands", it).add(setupCommand) } } /** Replacement skill configuration installed for each new session. */ @@ -1123,7 +1122,7 @@ private constructor( */ fun addSkill(skill: HostedSkillParam) = apply { skills = - (skills ?: JsonField.of(mutableListOf())).also { + (skills?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("skills", it).add(skill) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionCreateParams.kt index 2208acb13..233d27f5c 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionCreateParams.kt @@ -881,9 +881,8 @@ private constructor( */ fun addVaultId(vaultId: String) = apply { vaultIds = - (vaultIds ?: JsonField.of(mutableListOf())).also { - checkKnown("vaultIds", it).add(vaultId) - } + (vaultIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("vaultIds", it).add(vaultId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventCreateParams.kt index 2a98059f5..ee3b17181 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventCreateParams.kt @@ -429,7 +429,7 @@ private constructor( */ fun addEvent(event: AgentSessionInputParam) = apply { events = - (events ?: JsonField.of(mutableListOf())).also { + (events?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("events", it).add(event) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantCreateParams.kt index 3b1d87d83..e2bfc358a 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantCreateParams.kt @@ -1369,7 +1369,7 @@ private constructor( */ fun addTool(tool: AssistantTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantUpdateParams.kt index 3f7c8229a..23d6d87c7 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantUpdateParams.kt @@ -1356,7 +1356,7 @@ private constructor( */ fun addTool(tool: AssistantTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseCreateParams.kt index d1e57dbfb..8cd8a7a2f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseCreateParams.kt @@ -2866,9 +2866,9 @@ private constructor( */ fun addContextManagement(contextManagement: ContextManagement) = apply { this.contextManagement = - (this.contextManagement ?: JsonField.of(mutableListOf())).also { - checkKnown("contextManagement", it).add(contextManagement) - } + (this.contextManagement?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("contextManagement", it).add(contextManagement) } } /** @@ -2950,9 +2950,8 @@ private constructor( */ fun addInclude(include: BetaResponseIncludable) = apply { this.include = - (this.include ?: JsonField.of(mutableListOf())).also { - checkKnown("include", it).add(include) - } + (this.include?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("include", it).add(include) } } /** @@ -3612,7 +3611,7 @@ private constructor( */ fun addTool(tool: BetaTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountParams.kt index d37b70c15..540131a6f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountParams.kt @@ -1619,7 +1619,7 @@ private constructor( */ fun addTool(tool: BetaTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateAndRunParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateAndRunParams.kt index cdf42c3d2..4726bc8ea 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateAndRunParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateAndRunParams.kt @@ -1793,7 +1793,7 @@ private constructor( */ fun addTool(tool: AssistantTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateParams.kt index 54c50c011..6b699ce04 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateParams.kt @@ -489,9 +489,8 @@ private constructor( */ fun addMessage(message: Message) = apply { messages = - (messages ?: JsonField.of(mutableListOf())).also { - checkKnown("messages", it).add(message) - } + (messages?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("messages", it).add(message) } } /** diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageCreateParams.kt index a0c9296eb..c222c32b8 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageCreateParams.kt @@ -613,9 +613,8 @@ private constructor( */ fun addAttachment(attachment: Attachment) = apply { attachments = - (attachments ?: JsonField.of(mutableListOf())).also { - checkKnown("attachments", it).add(attachment) - } + (attachments?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("attachments", it).add(attachment) } } /** diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunCreateParams.kt index c4596c0f5..6745073b6 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunCreateParams.kt @@ -1658,9 +1658,9 @@ private constructor( */ fun addAdditionalMessage(additionalMessage: AdditionalMessage) = apply { additionalMessages = - (additionalMessages ?: JsonField.of(mutableListOf())).also { - checkKnown("additionalMessages", it).add(additionalMessage) - } + (additionalMessages?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("additionalMessages", it).add(additionalMessage) } } /** @@ -2014,7 +2014,7 @@ private constructor( */ fun addTool(tool: AssistantTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunSubmitToolOutputsParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunSubmitToolOutputsParams.kt index 481c1448f..45e80f2b3 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunSubmitToolOutputsParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunSubmitToolOutputsParams.kt @@ -377,9 +377,8 @@ private constructor( */ fun addToolOutput(toolOutput: ToolOutput) = apply { toolOutputs = - (toolOutputs ?: JsonField.of(mutableListOf())).also { - checkKnown("toolOutputs", it).add(toolOutput) - } + (toolOutputs?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("toolOutputs", it).add(toolOutput) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt index aaf1210b1..d19f9e1d8 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt @@ -3268,9 +3268,8 @@ private constructor( */ fun addMessage(message: ChatCompletionMessageParam) = apply { messages = - (messages ?: JsonField.of(mutableListOf())).also { - checkKnown("messages", it).add(message) - } + (messages?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("messages", it).add(message) } } /** @@ -3594,9 +3593,8 @@ private constructor( @Deprecated("deprecated") fun addFunction(function: Function) = apply { functions = - (functions ?: JsonField.of(mutableListOf())).also { - checkKnown("functions", it).add(function) - } + (functions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("functions", it).add(function) } } /** @@ -3774,9 +3772,8 @@ private constructor( */ fun addModality(modality: Modality) = apply { modalities = - (modalities ?: JsonField.of(mutableListOf())).also { - checkKnown("modalities", it).add(modality) - } + (modalities?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("modalities", it).add(modality) } } /** Configuration for running moderation on the request input and generated output. */ @@ -4337,7 +4334,7 @@ private constructor( */ fun addTool(tool: ChatCompletionTool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateParams.kt index 8676186e5..968f34a83 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateParams.kt @@ -684,7 +684,7 @@ private constructor( */ fun addFileId(fileId: String) = apply { fileIds = - (fileIds ?: JsonField.of(mutableListOf())).also { + (fileIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("fileIds", it).add(fileId) } } @@ -760,7 +760,7 @@ private constructor( */ fun addSkill(skill: Skill) = apply { skills = - (skills ?: JsonField.of(mutableListOf())).also { + (skills?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("skills", it).add(skill) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationCreateParams.kt index 19098f4ad..d0d274cb6 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationCreateParams.kt @@ -705,7 +705,7 @@ private constructor( */ fun addItem(item: ResponseInputItem) = apply { items = - (items ?: JsonField.of(mutableListOf())).also { + (items?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("items", it).add(item) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemCreateParams.kt index 9ae674206..a4668c287 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemCreateParams.kt @@ -700,7 +700,7 @@ private constructor( */ fun addItem(item: ResponseInputItem) = apply { items = - (items ?: JsonField.of(mutableListOf())).also { + (items?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("items", it).add(item) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionCreateParams.kt index 1247fadd8..6f41d0538 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionCreateParams.kt @@ -610,9 +610,8 @@ private constructor( */ fun addQuestion(question: Question) = apply { questions = - (questions ?: JsonField.of(mutableListOf())).also { - checkKnown("questions", it).add(question) - } + (questions?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("questions", it).add(question) } } /** Alias for calling [addQuestion] with `Question.ofPredicate(predicate)`. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateParams.kt index 95de195eb..fb748b736 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateParams.kt @@ -675,9 +675,9 @@ private constructor( */ fun addTestingCriterion(testingCriterion: TestingCriterion) = apply { testingCriteria = - (testingCriteria ?: JsonField.of(mutableListOf())).also { - checkKnown("testingCriteria", it).add(testingCriterion) - } + (testingCriteria?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("testingCriteria", it).add(testingCriterion) } } /** diff --git a/openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateParams.kt index c7e40e832..84d36baea 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateParams.kt @@ -371,9 +371,8 @@ private constructor( */ fun addProjectId(projectId: String) = apply { projectIds = - (projectIds ?: JsonField.of(mutableListOf())).also { - checkKnown("projectIds", it).add(projectId) - } + (projectIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("projectIds", it).add(projectId) } } fun additionalProperties(additionalProperties: Map) = apply { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobCreateParams.kt index 3d7da118f..dee0db7a1 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobCreateParams.kt @@ -1029,9 +1029,8 @@ private constructor( */ fun addIntegration(integration: Integration) = apply { integrations = - (integrations ?: JsonField.of(mutableListOf())).also { - checkKnown("integrations", it).add(integration) - } + (integrations?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("integrations", it).add(integration) } } /** diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/LiveWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/LiveWebSocketBinding.kt new file mode 100644 index 000000000..2fc5114a8 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/LiveWebSocketBinding.kt @@ -0,0 +1,18 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object LiveWebSocketBinding { + fun create(): + WebSocketBinding = + WebSocketBinding( + listOf("live", "sessions"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.live.ServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkWebSocketBinding.kt new file mode 100644 index 000000000..b9f03be86 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkWebSocketBinding.kt @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.forks + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object ForkWebSocketBinding { + fun create( + `sessionId`: String + ): WebSocketBinding< + com.openai.models.live.forks.ForkClientEvent, + com.openai.models.live.forks.ForkServerEvent, + > = + WebSocketBinding( + listOf("live", "sessions", `sessionId`.toString(), "fork"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.live.forks.ForkServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandClientEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandClientEvent.kt new file mode 100644 index 000000000..3b2bf1b2f --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandClientEvent.kt @@ -0,0 +1,724 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.sideband + +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 com.openai.core.BaseDeserializer +import com.openai.core.BaseSerializer +import com.openai.core.JsonValue +import com.openai.core.getOrThrow +import com.openai.errors.OpenAIInvalidDataException +import com.openai.models.live.CommentaryAppendEvent +import com.openai.models.live.InputAudioMuteEvent +import com.openai.models.live.InputAudioUnmuteEvent +import com.openai.models.live.InstructionsAppendEvent +import com.openai.models.live.OutputAudioFixturePlayEvent +import com.openai.models.live.ResponseCreateEvent +import com.openai.models.live.ResponseItemCreateEvent +import com.openai.models.live.SessionCloseEvent +import com.openai.models.live.SessionUpdateEvent +import com.openai.models.live.ThinkingAppendEvent +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Client events accepted by an attached Live sideband WebSocket. The session has already started. + * Send input audio over the primary connection. + */ +@JsonDeserialize(using = SidebandClientEvent.Deserializer::class) +@JsonSerialize(using = SidebandClientEvent.Serializer::class) +class SidebandClientEvent +private constructor( + private val sessionUpdate: SessionUpdateEvent? = null, + private val sessionInputAudioMute: InputAudioMuteEvent? = null, + private val sessionInputAudioUnmute: InputAudioUnmuteEvent? = null, + private val sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent? = null, + private val sessionInstructionsAppend: InstructionsAppendEvent? = null, + private val sessionThinkingAppend: ThinkingAppendEvent? = null, + private val sessionCommentaryAppend: CommentaryAppendEvent? = null, + private val responseItemCreate: ResponseItemCreateEvent? = null, + private val responseCreate: ResponseCreateEvent? = null, + private val sessionClose: SessionCloseEvent? = null, + private val _json: JsonValue? = null, +) { + + /** + * Update the delegation settings of an active Live session. The server acknowledges accepted + * changes with `session.updated`. + */ + fun sessionUpdate(): Optional = Optional.ofNullable(sessionUpdate) + + /** + * Mute audio input to the Live model without closing the session. The server acknowledges with + * `session.input_audio.muted`. + */ + fun sessionInputAudioMute(): Optional = + Optional.ofNullable(sessionInputAudioMute) + + /** + * Resume audio input to a Live model after muting it. The server acknowledges with + * `session.input_audio.unmuted`. + */ + fun sessionInputAudioUnmute(): Optional = + Optional.ofNullable(sessionInputAudioUnmute) + + /** + * Play one complete audio fixture through the session output. Send this event over the primary + * WebSocket or a server sideband. Audio must be raw mono PCM16 little-endian at 24 kHz, + * regardless of the session audio format, and at most 30 seconds. The entire payload is + * validated before model speech is interrupted and input is muted. Only one fixture can be + * active. fixture.playing acknowledges acceptance and fixture.played reports server mixer + * drain. These notifications are not exact transport-media boundaries. Input arriving during + * playback is dropped and not replayed. + */ + fun sessionOutputAudioFixturePlay(): Optional = + Optional.ofNullable(sessionOutputAudioFixturePlay) + + /** + * Append instructions to the Live conversation while it is running, optionally associating them + * with an existing client delegation. + */ + fun sessionInstructionsAppend(): Optional = + Optional.ofNullable(sessionInstructionsAppend) + + /** + * Provide silent reasoning or progress context to the Live model, optionally for an existing + * client delegation. + */ + fun sessionThinkingAppend(): Optional = + Optional.ofNullable(sessionThinkingAppend) + + /** + * Provide context the Live model can communicate to the user, optionally for an existing client + * delegation. + */ + fun sessionCommentaryAppend(): Optional = + Optional.ofNullable(sessionCommentaryAppend) + + /** + * Add an input item to the Live session’s Responses backend. Requires Responses delegation; use + * `response.create` to request a response. + */ + fun responseItemCreate(): Optional = + Optional.ofNullable(responseItemCreate) + + /** + * Request a response from the Live session’s Responses backend, or continue a delegated + * response waiting for tool results. Requires Responses delegation. + */ + fun responseCreate(): Optional = Optional.ofNullable(responseCreate) + + /** + * Request that the Live session close. The terminal `session.closed` event contains the close + * reason and final usage. + */ + fun sessionClose(): Optional = Optional.ofNullable(sessionClose) + + fun isSessionUpdate(): Boolean = sessionUpdate != null + + fun isSessionInputAudioMute(): Boolean = sessionInputAudioMute != null + + fun isSessionInputAudioUnmute(): Boolean = sessionInputAudioUnmute != null + + fun isSessionOutputAudioFixturePlay(): Boolean = sessionOutputAudioFixturePlay != null + + fun isSessionInstructionsAppend(): Boolean = sessionInstructionsAppend != null + + fun isSessionThinkingAppend(): Boolean = sessionThinkingAppend != null + + fun isSessionCommentaryAppend(): Boolean = sessionCommentaryAppend != null + + fun isResponseItemCreate(): Boolean = responseItemCreate != null + + fun isResponseCreate(): Boolean = responseCreate != null + + fun isSessionClose(): Boolean = sessionClose != null + + /** + * Update the delegation settings of an active Live session. The server acknowledges accepted + * changes with `session.updated`. + */ + fun asSessionUpdate(): SessionUpdateEvent = sessionUpdate.getOrThrow("sessionUpdate") + + /** + * Mute audio input to the Live model without closing the session. The server acknowledges with + * `session.input_audio.muted`. + */ + fun asSessionInputAudioMute(): InputAudioMuteEvent = + sessionInputAudioMute.getOrThrow("sessionInputAudioMute") + + /** + * Resume audio input to a Live model after muting it. The server acknowledges with + * `session.input_audio.unmuted`. + */ + fun asSessionInputAudioUnmute(): InputAudioUnmuteEvent = + sessionInputAudioUnmute.getOrThrow("sessionInputAudioUnmute") + + /** + * Play one complete audio fixture through the session output. Send this event over the primary + * WebSocket or a server sideband. Audio must be raw mono PCM16 little-endian at 24 kHz, + * regardless of the session audio format, and at most 30 seconds. The entire payload is + * validated before model speech is interrupted and input is muted. Only one fixture can be + * active. fixture.playing acknowledges acceptance and fixture.played reports server mixer + * drain. These notifications are not exact transport-media boundaries. Input arriving during + * playback is dropped and not replayed. + */ + fun asSessionOutputAudioFixturePlay(): OutputAudioFixturePlayEvent = + sessionOutputAudioFixturePlay.getOrThrow("sessionOutputAudioFixturePlay") + + /** + * Append instructions to the Live conversation while it is running, optionally associating them + * with an existing client delegation. + */ + fun asSessionInstructionsAppend(): InstructionsAppendEvent = + sessionInstructionsAppend.getOrThrow("sessionInstructionsAppend") + + /** + * Provide silent reasoning or progress context to the Live model, optionally for an existing + * client delegation. + */ + fun asSessionThinkingAppend(): ThinkingAppendEvent = + sessionThinkingAppend.getOrThrow("sessionThinkingAppend") + + /** + * Provide context the Live model can communicate to the user, optionally for an existing client + * delegation. + */ + fun asSessionCommentaryAppend(): CommentaryAppendEvent = + sessionCommentaryAppend.getOrThrow("sessionCommentaryAppend") + + /** + * Add an input item to the Live session’s Responses backend. Requires Responses delegation; use + * `response.create` to request a response. + */ + fun asResponseItemCreate(): ResponseItemCreateEvent = + responseItemCreate.getOrThrow("responseItemCreate") + + /** + * Request a response from the Live session’s Responses backend, or continue a delegated + * response waiting for tool results. Requires Responses delegation. + */ + fun asResponseCreate(): ResponseCreateEvent = responseCreate.getOrThrow("responseCreate") + + /** + * Request that the Live session close. The terminal `session.closed` event contains the close + * reason and final usage. + */ + fun asSessionClose(): SessionCloseEvent = sessionClose.getOrThrow("sessionClose") + + 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.openai.core.JsonValue; + * import java.util.Optional; + * + * Optional result = sidebandClientEvent.accept(new SidebandClientEvent.Visitor>() { + * @Override + * public Optional visitSessionUpdate(SessionUpdateEvent sessionUpdate) { + * return Optional.of(sessionUpdate.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws OpenAIInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and + * the current variant is unknown. + */ + fun accept(visitor: Visitor): T = + when { + sessionUpdate != null -> visitor.visitSessionUpdate(sessionUpdate) + sessionInputAudioMute != null -> + visitor.visitSessionInputAudioMute(sessionInputAudioMute) + sessionInputAudioUnmute != null -> + visitor.visitSessionInputAudioUnmute(sessionInputAudioUnmute) + sessionOutputAudioFixturePlay != null -> + visitor.visitSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) + sessionInstructionsAppend != null -> + visitor.visitSessionInstructionsAppend(sessionInstructionsAppend) + sessionThinkingAppend != null -> + visitor.visitSessionThinkingAppend(sessionThinkingAppend) + sessionCommentaryAppend != null -> + visitor.visitSessionCommentaryAppend(sessionCommentaryAppend) + responseItemCreate != null -> visitor.visitResponseItemCreate(responseItemCreate) + responseCreate != null -> visitor.visitResponseCreate(responseCreate) + sessionClose != null -> visitor.visitSessionClose(sessionClose) + 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 OpenAIInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): SidebandClientEvent = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent) { + sessionUpdate.validate() + } + + override fun visitSessionInputAudioMute( + sessionInputAudioMute: InputAudioMuteEvent + ) { + sessionInputAudioMute.validate() + } + + override fun visitSessionInputAudioUnmute( + sessionInputAudioUnmute: InputAudioUnmuteEvent + ) { + sessionInputAudioUnmute.validate() + } + + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) { + sessionOutputAudioFixturePlay.validate() + } + + override fun visitSessionInstructionsAppend( + sessionInstructionsAppend: InstructionsAppendEvent + ) { + sessionInstructionsAppend.validate() + } + + override fun visitSessionThinkingAppend( + sessionThinkingAppend: ThinkingAppendEvent + ) { + sessionThinkingAppend.validate() + } + + override fun visitSessionCommentaryAppend( + sessionCommentaryAppend: CommentaryAppendEvent + ) { + sessionCommentaryAppend.validate() + } + + override fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent) { + responseItemCreate.validate() + } + + override fun visitResponseCreate(responseCreate: ResponseCreateEvent) { + responseCreate.validate() + } + + override fun visitSessionClose(sessionClose: SessionCloseEvent) { + sessionClose.validate() + } + } + ) + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: OpenAIInvalidDataException) { + 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 visitSessionUpdate(sessionUpdate: SessionUpdateEvent) = + sessionUpdate.validity() + + override fun visitSessionInputAudioMute( + sessionInputAudioMute: InputAudioMuteEvent + ) = sessionInputAudioMute.validity() + + override fun visitSessionInputAudioUnmute( + sessionInputAudioUnmute: InputAudioUnmuteEvent + ) = sessionInputAudioUnmute.validity() + + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) = sessionOutputAudioFixturePlay.validity() + + override fun visitSessionInstructionsAppend( + sessionInstructionsAppend: InstructionsAppendEvent + ) = sessionInstructionsAppend.validity() + + override fun visitSessionThinkingAppend( + sessionThinkingAppend: ThinkingAppendEvent + ) = sessionThinkingAppend.validity() + + override fun visitSessionCommentaryAppend( + sessionCommentaryAppend: CommentaryAppendEvent + ) = sessionCommentaryAppend.validity() + + override fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent) = + responseItemCreate.validity() + + override fun visitResponseCreate(responseCreate: ResponseCreateEvent) = + responseCreate.validity() + + override fun visitSessionClose(sessionClose: SessionCloseEvent) = + sessionClose.validity() + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is SidebandClientEvent && + sessionUpdate == other.sessionUpdate && + sessionInputAudioMute == other.sessionInputAudioMute && + sessionInputAudioUnmute == other.sessionInputAudioUnmute && + sessionOutputAudioFixturePlay == other.sessionOutputAudioFixturePlay && + sessionInstructionsAppend == other.sessionInstructionsAppend && + sessionThinkingAppend == other.sessionThinkingAppend && + sessionCommentaryAppend == other.sessionCommentaryAppend && + responseItemCreate == other.responseItemCreate && + responseCreate == other.responseCreate && + sessionClose == other.sessionClose + } + + override fun hashCode(): Int = + Objects.hash( + sessionUpdate, + sessionInputAudioMute, + sessionInputAudioUnmute, + sessionOutputAudioFixturePlay, + sessionInstructionsAppend, + sessionThinkingAppend, + sessionCommentaryAppend, + responseItemCreate, + responseCreate, + sessionClose, + ) + + override fun toString(): String = + when { + sessionUpdate != null -> "SidebandClientEvent{sessionUpdate=$sessionUpdate}" + sessionInputAudioMute != null -> + "SidebandClientEvent{sessionInputAudioMute=$sessionInputAudioMute}" + sessionInputAudioUnmute != null -> + "SidebandClientEvent{sessionInputAudioUnmute=$sessionInputAudioUnmute}" + sessionOutputAudioFixturePlay != null -> + "SidebandClientEvent{sessionOutputAudioFixturePlay=$sessionOutputAudioFixturePlay}" + sessionInstructionsAppend != null -> + "SidebandClientEvent{sessionInstructionsAppend=$sessionInstructionsAppend}" + sessionThinkingAppend != null -> + "SidebandClientEvent{sessionThinkingAppend=$sessionThinkingAppend}" + sessionCommentaryAppend != null -> + "SidebandClientEvent{sessionCommentaryAppend=$sessionCommentaryAppend}" + responseItemCreate != null -> + "SidebandClientEvent{responseItemCreate=$responseItemCreate}" + responseCreate != null -> "SidebandClientEvent{responseCreate=$responseCreate}" + sessionClose != null -> "SidebandClientEvent{sessionClose=$sessionClose}" + _json != null -> "SidebandClientEvent{_unknown=$_json}" + else -> throw IllegalStateException("Invalid SidebandClientEvent") + } + + companion object { + + /** + * Update the delegation settings of an active Live session. The server acknowledges + * accepted changes with `session.updated`. + */ + @JvmStatic + fun ofSessionUpdate(sessionUpdate: SessionUpdateEvent) = + SidebandClientEvent(sessionUpdate = sessionUpdate) + + /** + * Mute audio input to the Live model without closing the session. The server acknowledges + * with `session.input_audio.muted`. + */ + @JvmStatic + fun ofSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent) = + SidebandClientEvent(sessionInputAudioMute = sessionInputAudioMute) + + /** + * Resume audio input to a Live model after muting it. The server acknowledges with + * `session.input_audio.unmuted`. + */ + @JvmStatic + fun ofSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent) = + SidebandClientEvent(sessionInputAudioUnmute = sessionInputAudioUnmute) + + /** + * Play one complete audio fixture through the session output. Send this event over the + * primary WebSocket or a server sideband. Audio must be raw mono PCM16 little-endian at 24 + * kHz, regardless of the session audio format, and at most 30 seconds. The entire payload + * is validated before model speech is interrupted and input is muted. Only one fixture can + * be active. fixture.playing acknowledges acceptance and fixture.played reports server + * mixer drain. These notifications are not exact transport-media boundaries. Input arriving + * during playback is dropped and not replayed. + */ + @JvmStatic + fun ofSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) = SidebandClientEvent(sessionOutputAudioFixturePlay = sessionOutputAudioFixturePlay) + + /** + * Append instructions to the Live conversation while it is running, optionally associating + * them with an existing client delegation. + */ + @JvmStatic + fun ofSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent) = + SidebandClientEvent(sessionInstructionsAppend = sessionInstructionsAppend) + + /** + * Provide silent reasoning or progress context to the Live model, optionally for an + * existing client delegation. + */ + @JvmStatic + fun ofSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent) = + SidebandClientEvent(sessionThinkingAppend = sessionThinkingAppend) + + /** + * Provide context the Live model can communicate to the user, optionally for an existing + * client delegation. + */ + @JvmStatic + fun ofSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent) = + SidebandClientEvent(sessionCommentaryAppend = sessionCommentaryAppend) + + /** + * Add an input item to the Live session’s Responses backend. Requires Responses delegation; + * use `response.create` to request a response. + */ + @JvmStatic + fun ofResponseItemCreate(responseItemCreate: ResponseItemCreateEvent) = + SidebandClientEvent(responseItemCreate = responseItemCreate) + + /** + * Request a response from the Live session’s Responses backend, or continue a delegated + * response waiting for tool results. Requires Responses delegation. + */ + @JvmStatic + fun ofResponseCreate(responseCreate: ResponseCreateEvent) = + SidebandClientEvent(responseCreate = responseCreate) + + /** + * Request that the Live session close. The terminal `session.closed` event contains the + * close reason and final usage. + */ + @JvmStatic + fun ofSessionClose(sessionClose: SessionCloseEvent) = + SidebandClientEvent(sessionClose = sessionClose) + } + + /** + * An interface that defines how to map each variant of [SidebandClientEvent] to a value of type + * [T]. + */ + interface Visitor { + + /** + * Update the delegation settings of an active Live session. The server acknowledges + * accepted changes with `session.updated`. + */ + fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent): T + + /** + * Mute audio input to the Live model without closing the session. The server acknowledges + * with `session.input_audio.muted`. + */ + fun visitSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent): T + + /** + * Resume audio input to a Live model after muting it. The server acknowledges with + * `session.input_audio.unmuted`. + */ + fun visitSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent): T + + /** + * Play one complete audio fixture through the session output. Send this event over the + * primary WebSocket or a server sideband. Audio must be raw mono PCM16 little-endian at 24 + * kHz, regardless of the session audio format, and at most 30 seconds. The entire payload + * is validated before model speech is interrupted and input is muted. Only one fixture can + * be active. fixture.playing acknowledges acceptance and fixture.played reports server + * mixer drain. These notifications are not exact transport-media boundaries. Input arriving + * during playback is dropped and not replayed. + */ + fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ): T + + /** + * Append instructions to the Live conversation while it is running, optionally associating + * them with an existing client delegation. + */ + fun visitSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent): T + + /** + * Provide silent reasoning or progress context to the Live model, optionally for an + * existing client delegation. + */ + fun visitSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent): T + + /** + * Provide context the Live model can communicate to the user, optionally for an existing + * client delegation. + */ + fun visitSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent): T + + /** + * Add an input item to the Live session’s Responses backend. Requires Responses delegation; + * use `response.create` to request a response. + */ + fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent): T + + /** + * Request a response from the Live session’s Responses backend, or continue a delegated + * response waiting for tool results. Requires Responses delegation. + */ + fun visitResponseCreate(responseCreate: ResponseCreateEvent): T + + /** + * Request that the Live session close. The terminal `session.closed` event contains the + * close reason and final usage. + */ + fun visitSessionClose(sessionClose: SessionCloseEvent): T + + /** + * Maps an unknown variant of [SidebandClientEvent] to a value of type [T]. + * + * An instance of [SidebandClientEvent] 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 OpenAIInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw OpenAIInvalidDataException("Unknown SidebandClientEvent") + } + } + + internal class Deserializer : + BaseDeserializer(SidebandClientEvent::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): SidebandClientEvent { + val json = JsonValue.fromJsonNode(node) + val type = json.asObject().getOrNull()?.get("type")?.asString()?.getOrNull() + + when (type) { + "session.update" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionUpdate = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.input_audio.mute" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionInputAudioMute = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.input_audio.unmute" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionInputAudioUnmute = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.output_audio.fixture.play" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { + SidebandClientEvent(sessionOutputAudioFixturePlay = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.instructions.append" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionInstructionsAppend = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.thinking.append" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionThinkingAppend = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.commentary.append" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionCommentaryAppend = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "response.item.create" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(responseItemCreate = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "response.create" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(responseCreate = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + "session.close" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandClientEvent(sessionClose = it, _json = json) + } ?: SidebandClientEvent(_json = json) + } + } + + return SidebandClientEvent(_json = json) + } + } + + internal class Serializer : BaseSerializer(SidebandClientEvent::class) { + + override fun serialize( + value: SidebandClientEvent, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.sessionUpdate != null -> generator.writeObject(value.sessionUpdate) + value.sessionInputAudioMute != null -> + generator.writeObject(value.sessionInputAudioMute) + value.sessionInputAudioUnmute != null -> + generator.writeObject(value.sessionInputAudioUnmute) + value.sessionOutputAudioFixturePlay != null -> + generator.writeObject(value.sessionOutputAudioFixturePlay) + value.sessionInstructionsAppend != null -> + generator.writeObject(value.sessionInstructionsAppend) + value.sessionThinkingAppend != null -> + generator.writeObject(value.sessionThinkingAppend) + value.sessionCommentaryAppend != null -> + generator.writeObject(value.sessionCommentaryAppend) + value.responseItemCreate != null -> generator.writeObject(value.responseItemCreate) + value.responseCreate != null -> generator.writeObject(value.responseCreate) + value.sessionClose != null -> generator.writeObject(value.sessionClose) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid SidebandClientEvent") + } + } + } +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandServerEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandServerEvent.kt new file mode 100644 index 000000000..ed51b2a8a --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandServerEvent.kt @@ -0,0 +1,1123 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.sideband + +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 com.openai.core.BaseDeserializer +import com.openai.core.BaseSerializer +import com.openai.core.JsonValue +import com.openai.core.getOrThrow +import com.openai.errors.OpenAIInvalidDataException +import com.openai.models.live.CommentaryAppendedEvent +import com.openai.models.live.DelegationCreatedEvent +import com.openai.models.live.ErrorEvent +import com.openai.models.live.InfoEvent +import com.openai.models.live.InputAudioMutedEvent +import com.openai.models.live.InputAudioUnmutedEvent +import com.openai.models.live.InputTranscriptDeltaEvent +import com.openai.models.live.InstructionsAppendedEvent +import com.openai.models.live.OutputAudioFixturePlayedEvent +import com.openai.models.live.OutputAudioFixturePlayingEvent +import com.openai.models.live.OutputTranscriptDeltaEvent +import com.openai.models.live.ResponseEvent +import com.openai.models.live.SessionClosedEvent +import com.openai.models.live.SessionStartedEvent +import com.openai.models.live.SessionUpdatedEvent +import com.openai.models.live.SessionUsageUpdatedEvent +import com.openai.models.live.ThinkingAppendedEvent +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * Server events received by an attached Live sideband WebSocket. Audio deltas are delivered over + * the primary connection. + */ +@JsonDeserialize(using = SidebandServerEvent.Deserializer::class) +@JsonSerialize(using = SidebandServerEvent.Serializer::class) +class SidebandServerEvent +private constructor( + private val sessionStarted: SessionStartedEvent? = null, + private val sessionUpdated: SessionUpdatedEvent? = null, + private val sessionInputAudioMuted: InputAudioMutedEvent? = null, + private val sessionInputAudioUnmuted: InputAudioUnmutedEvent? = null, + private val sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent? = null, + private val sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent? = null, + private val sessionInstructionsAppended: InstructionsAppendedEvent? = null, + private val sessionThinkingAppended: ThinkingAppendedEvent? = null, + private val sessionCommentaryAppended: CommentaryAppendedEvent? = null, + private val sessionInputTranscriptDelta: InputTranscriptDeltaEvent? = null, + private val sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent? = null, + private val sessionDelegationCreated: DelegationCreatedEvent? = null, + private val responseEvent: ResponseEvent? = null, + private val sessionUsageUpdated: SessionUsageUpdatedEvent? = null, + private val sessionClosed: SessionClosedEvent? = null, + private val errorEvent: ErrorEvent? = null, + private val infoEvent: InfoEvent? = null, + private val _json: JsonValue? = null, +) { + + /** + * Returned when a Live session has started. Contains the resolved session configuration, + * including server defaults. + */ + fun sessionStarted(): Optional = Optional.ofNullable(sessionStarted) + + /** + * Returned when a Live session update is accepted. Contains the resolved session configuration + * after the update. + */ + fun sessionUpdated(): Optional = Optional.ofNullable(sessionUpdated) + + /** + * Returned when a session.input_audio.mute command is accepted. Input audio is no longer sent + * to the model; sideband audio reflection continues. + */ + fun sessionInputAudioMuted(): Optional = + Optional.ofNullable(sessionInputAudioMuted) + + /** + * Returned when a session.input_audio.unmute command is accepted. Input audio is sent to the + * model again. + */ + fun sessionInputAudioUnmuted(): Optional = + Optional.ofNullable(sessionInputAudioUnmuted) + + /** + * The existing playback path has accepted the validated audio fixture. This acknowledgment does + * not identify when the listener receives the first sample. The optional client_event_id echoes + * the play command. + */ + fun sessionOutputAudioFixturePlaying(): Optional = + Optional.ofNullable(sessionOutputAudioFixturePlaying) + + /** + * The complete audio fixture has drained from the server mixer and normal inference has + * resumed. Transport buffers may still contain audio; this event does not prove listener-side + * completion. The optional client_event_id echoes the play command. This event is not emitted + * for canceled or failed playback. + */ + fun sessionOutputAudioFixturePlayed(): Optional = + Optional.ofNullable(sessionOutputAudioFixturePlayed) + + /** + * Returned when a session.instructions.append command is accepted into the Live session + * timeline. Acknowledges the appended instructions without guaranteeing that the model has + * acted on them. + */ + fun sessionInstructionsAppended(): Optional = + Optional.ofNullable(sessionInstructionsAppended) + + /** + * Returned when a session.thinking.append command is accepted into the Live session timeline. + * Acknowledges the added reasoning context without guaranteeing any spoken output. + */ + fun sessionThinkingAppended(): Optional = + Optional.ofNullable(sessionThinkingAppended) + + /** + * Returned when a session.commentary.append command is accepted into the Live session timeline. + * Acknowledges the added commentary without guaranteeing exact wording or completed audio + * playback. + */ + fun sessionCommentaryAppended(): Optional = + Optional.ofNullable(sessionCommentaryAppended) + + /** + * A transcript fragment for user input audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done event. + */ + fun sessionInputTranscriptDelta(): Optional = + Optional.ofNullable(sessionInputTranscriptDelta) + + /** + * A transcript fragment for assistant output audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done event. + */ + fun sessionOutputTranscriptDelta(): Optional = + Optional.ofNullable(sessionOutputTranscriptDelta) + + /** + * Returned when the Live model delegates work to your application or a Responses backend. + * Contains delegation metadata and the position on the session timeline where the work was + * delegated. + */ + fun sessionDelegationCreated(): Optional = + Optional.ofNullable(sessionDelegationCreated) + + /** + * A streaming Responses API event from a backend delegated to by the Live session. Use the + * outer delegation_id to associate the nested stream with its Live delegation. + */ + fun responseEvent(): Optional = Optional.ofNullable(responseEvent) + + /** + * Reports cumulative Live audio usage and, when available, the most recent context-window + * usage. Delegated Responses token usage is reported separately in response.event events. + */ + fun sessionUsageUpdated(): Optional = + Optional.ofNullable(sessionUsageUpdated) + + /** + * Returned after the Live session finishes finalizing, with the close reason, final session + * snapshot, and cumulative audio usage. A connection closing without this event does not + * confirm successful finalization. + */ + fun sessionClosed(): Optional = Optional.ofNullable(sessionClosed) + + /** + * Reports an error in the Live session, such as an invalid client command. Use + * error.client_event_id, when present, to identify the command that caused the error. + */ + fun errorEvent(): Optional = Optional.ofNullable(errorEvent) + + /** + * An informational notice about the Live session, such as the event permissions applied to a + * frontend data channel. + */ + fun infoEvent(): Optional = Optional.ofNullable(infoEvent) + + fun isSessionStarted(): Boolean = sessionStarted != null + + fun isSessionUpdated(): Boolean = sessionUpdated != null + + fun isSessionInputAudioMuted(): Boolean = sessionInputAudioMuted != null + + fun isSessionInputAudioUnmuted(): Boolean = sessionInputAudioUnmuted != null + + fun isSessionOutputAudioFixturePlaying(): Boolean = sessionOutputAudioFixturePlaying != null + + fun isSessionOutputAudioFixturePlayed(): Boolean = sessionOutputAudioFixturePlayed != null + + fun isSessionInstructionsAppended(): Boolean = sessionInstructionsAppended != null + + fun isSessionThinkingAppended(): Boolean = sessionThinkingAppended != null + + fun isSessionCommentaryAppended(): Boolean = sessionCommentaryAppended != null + + fun isSessionInputTranscriptDelta(): Boolean = sessionInputTranscriptDelta != null + + fun isSessionOutputTranscriptDelta(): Boolean = sessionOutputTranscriptDelta != null + + fun isSessionDelegationCreated(): Boolean = sessionDelegationCreated != null + + fun isResponseEvent(): Boolean = responseEvent != null + + fun isSessionUsageUpdated(): Boolean = sessionUsageUpdated != null + + fun isSessionClosed(): Boolean = sessionClosed != null + + fun isErrorEvent(): Boolean = errorEvent != null + + fun isInfoEvent(): Boolean = infoEvent != null + + /** + * Returned when a Live session has started. Contains the resolved session configuration, + * including server defaults. + */ + fun asSessionStarted(): SessionStartedEvent = sessionStarted.getOrThrow("sessionStarted") + + /** + * Returned when a Live session update is accepted. Contains the resolved session configuration + * after the update. + */ + fun asSessionUpdated(): SessionUpdatedEvent = sessionUpdated.getOrThrow("sessionUpdated") + + /** + * Returned when a session.input_audio.mute command is accepted. Input audio is no longer sent + * to the model; sideband audio reflection continues. + */ + fun asSessionInputAudioMuted(): InputAudioMutedEvent = + sessionInputAudioMuted.getOrThrow("sessionInputAudioMuted") + + /** + * Returned when a session.input_audio.unmute command is accepted. Input audio is sent to the + * model again. + */ + fun asSessionInputAudioUnmuted(): InputAudioUnmutedEvent = + sessionInputAudioUnmuted.getOrThrow("sessionInputAudioUnmuted") + + /** + * The existing playback path has accepted the validated audio fixture. This acknowledgment does + * not identify when the listener receives the first sample. The optional client_event_id echoes + * the play command. + */ + fun asSessionOutputAudioFixturePlaying(): OutputAudioFixturePlayingEvent = + sessionOutputAudioFixturePlaying.getOrThrow("sessionOutputAudioFixturePlaying") + + /** + * The complete audio fixture has drained from the server mixer and normal inference has + * resumed. Transport buffers may still contain audio; this event does not prove listener-side + * completion. The optional client_event_id echoes the play command. This event is not emitted + * for canceled or failed playback. + */ + fun asSessionOutputAudioFixturePlayed(): OutputAudioFixturePlayedEvent = + sessionOutputAudioFixturePlayed.getOrThrow("sessionOutputAudioFixturePlayed") + + /** + * Returned when a session.instructions.append command is accepted into the Live session + * timeline. Acknowledges the appended instructions without guaranteeing that the model has + * acted on them. + */ + fun asSessionInstructionsAppended(): InstructionsAppendedEvent = + sessionInstructionsAppended.getOrThrow("sessionInstructionsAppended") + + /** + * Returned when a session.thinking.append command is accepted into the Live session timeline. + * Acknowledges the added reasoning context without guaranteeing any spoken output. + */ + fun asSessionThinkingAppended(): ThinkingAppendedEvent = + sessionThinkingAppended.getOrThrow("sessionThinkingAppended") + + /** + * Returned when a session.commentary.append command is accepted into the Live session timeline. + * Acknowledges the added commentary without guaranteeing exact wording or completed audio + * playback. + */ + fun asSessionCommentaryAppended(): CommentaryAppendedEvent = + sessionCommentaryAppended.getOrThrow("sessionCommentaryAppended") + + /** + * A transcript fragment for user input audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done event. + */ + fun asSessionInputTranscriptDelta(): InputTranscriptDeltaEvent = + sessionInputTranscriptDelta.getOrThrow("sessionInputTranscriptDelta") + + /** + * A transcript fragment for assistant output audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done event. + */ + fun asSessionOutputTranscriptDelta(): OutputTranscriptDeltaEvent = + sessionOutputTranscriptDelta.getOrThrow("sessionOutputTranscriptDelta") + + /** + * Returned when the Live model delegates work to your application or a Responses backend. + * Contains delegation metadata and the position on the session timeline where the work was + * delegated. + */ + fun asSessionDelegationCreated(): DelegationCreatedEvent = + sessionDelegationCreated.getOrThrow("sessionDelegationCreated") + + /** + * A streaming Responses API event from a backend delegated to by the Live session. Use the + * outer delegation_id to associate the nested stream with its Live delegation. + */ + fun asResponseEvent(): ResponseEvent = responseEvent.getOrThrow("responseEvent") + + /** + * Reports cumulative Live audio usage and, when available, the most recent context-window + * usage. Delegated Responses token usage is reported separately in response.event events. + */ + fun asSessionUsageUpdated(): SessionUsageUpdatedEvent = + sessionUsageUpdated.getOrThrow("sessionUsageUpdated") + + /** + * Returned after the Live session finishes finalizing, with the close reason, final session + * snapshot, and cumulative audio usage. A connection closing without this event does not + * confirm successful finalization. + */ + fun asSessionClosed(): SessionClosedEvent = sessionClosed.getOrThrow("sessionClosed") + + /** + * Reports an error in the Live session, such as an invalid client command. Use + * error.client_event_id, when present, to identify the command that caused the error. + */ + fun asErrorEvent(): ErrorEvent = errorEvent.getOrThrow("errorEvent") + + /** + * An informational notice about the Live session, such as the event permissions applied to a + * frontend data channel. + */ + fun asInfoEvent(): InfoEvent = infoEvent.getOrThrow("infoEvent") + + 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.openai.core.JsonValue; + * import java.util.Optional; + * + * Optional result = sidebandServerEvent.accept(new SidebandServerEvent.Visitor>() { + * @Override + * public Optional visitSessionStarted(SessionStartedEvent sessionStarted) { + * return Optional.of(sessionStarted.toString()); + * } + * + * // ... + * + * @Override + * public Optional unknown(JsonValue json) { + * // Or inspect the `json`. + * return Optional.empty(); + * } + * }); + * ``` + * + * @throws OpenAIInvalidDataException if [Visitor.unknown] is not overridden in [visitor] and + * the current variant is unknown. + */ + fun accept(visitor: Visitor): T = + when { + sessionStarted != null -> visitor.visitSessionStarted(sessionStarted) + sessionUpdated != null -> visitor.visitSessionUpdated(sessionUpdated) + sessionInputAudioMuted != null -> + visitor.visitSessionInputAudioMuted(sessionInputAudioMuted) + sessionInputAudioUnmuted != null -> + visitor.visitSessionInputAudioUnmuted(sessionInputAudioUnmuted) + sessionOutputAudioFixturePlaying != null -> + visitor.visitSessionOutputAudioFixturePlaying(sessionOutputAudioFixturePlaying) + sessionOutputAudioFixturePlayed != null -> + visitor.visitSessionOutputAudioFixturePlayed(sessionOutputAudioFixturePlayed) + sessionInstructionsAppended != null -> + visitor.visitSessionInstructionsAppended(sessionInstructionsAppended) + sessionThinkingAppended != null -> + visitor.visitSessionThinkingAppended(sessionThinkingAppended) + sessionCommentaryAppended != null -> + visitor.visitSessionCommentaryAppended(sessionCommentaryAppended) + sessionInputTranscriptDelta != null -> + visitor.visitSessionInputTranscriptDelta(sessionInputTranscriptDelta) + sessionOutputTranscriptDelta != null -> + visitor.visitSessionOutputTranscriptDelta(sessionOutputTranscriptDelta) + sessionDelegationCreated != null -> + visitor.visitSessionDelegationCreated(sessionDelegationCreated) + responseEvent != null -> visitor.visitResponseEvent(responseEvent) + sessionUsageUpdated != null -> visitor.visitSessionUsageUpdated(sessionUsageUpdated) + sessionClosed != null -> visitor.visitSessionClosed(sessionClosed) + errorEvent != null -> visitor.visitErrorEvent(errorEvent) + infoEvent != null -> visitor.visitInfoEvent(infoEvent) + 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 OpenAIInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): SidebandServerEvent = apply { + if (validated) { + return@apply + } + + accept( + object : Visitor { + override fun visitSessionStarted(sessionStarted: SessionStartedEvent) { + sessionStarted.validate() + } + + override fun visitSessionUpdated(sessionUpdated: SessionUpdatedEvent) { + sessionUpdated.validate() + } + + override fun visitSessionInputAudioMuted( + sessionInputAudioMuted: InputAudioMutedEvent + ) { + sessionInputAudioMuted.validate() + } + + override fun visitSessionInputAudioUnmuted( + sessionInputAudioUnmuted: InputAudioUnmutedEvent + ) { + sessionInputAudioUnmuted.validate() + } + + override fun visitSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ) { + sessionOutputAudioFixturePlaying.validate() + } + + override fun visitSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ) { + sessionOutputAudioFixturePlayed.validate() + } + + override fun visitSessionInstructionsAppended( + sessionInstructionsAppended: InstructionsAppendedEvent + ) { + sessionInstructionsAppended.validate() + } + + override fun visitSessionThinkingAppended( + sessionThinkingAppended: ThinkingAppendedEvent + ) { + sessionThinkingAppended.validate() + } + + override fun visitSessionCommentaryAppended( + sessionCommentaryAppended: CommentaryAppendedEvent + ) { + sessionCommentaryAppended.validate() + } + + override fun visitSessionInputTranscriptDelta( + sessionInputTranscriptDelta: InputTranscriptDeltaEvent + ) { + sessionInputTranscriptDelta.validate() + } + + override fun visitSessionOutputTranscriptDelta( + sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent + ) { + sessionOutputTranscriptDelta.validate() + } + + override fun visitSessionDelegationCreated( + sessionDelegationCreated: DelegationCreatedEvent + ) { + sessionDelegationCreated.validate() + } + + override fun visitResponseEvent(responseEvent: ResponseEvent) { + responseEvent.validate() + } + + override fun visitSessionUsageUpdated( + sessionUsageUpdated: SessionUsageUpdatedEvent + ) { + sessionUsageUpdated.validate() + } + + override fun visitSessionClosed(sessionClosed: SessionClosedEvent) { + sessionClosed.validate() + } + + override fun visitErrorEvent(errorEvent: ErrorEvent) { + errorEvent.validate() + } + + override fun visitInfoEvent(infoEvent: InfoEvent) { + infoEvent.validate() + } + } + ) + validated = true + } + + fun isValid(): Boolean = + try { + validate() + true + } catch (e: OpenAIInvalidDataException) { + 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 visitSessionStarted(sessionStarted: SessionStartedEvent) = + sessionStarted.validity() + + override fun visitSessionUpdated(sessionUpdated: SessionUpdatedEvent) = + sessionUpdated.validity() + + override fun visitSessionInputAudioMuted( + sessionInputAudioMuted: InputAudioMutedEvent + ) = sessionInputAudioMuted.validity() + + override fun visitSessionInputAudioUnmuted( + sessionInputAudioUnmuted: InputAudioUnmutedEvent + ) = sessionInputAudioUnmuted.validity() + + override fun visitSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ) = sessionOutputAudioFixturePlaying.validity() + + override fun visitSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ) = sessionOutputAudioFixturePlayed.validity() + + override fun visitSessionInstructionsAppended( + sessionInstructionsAppended: InstructionsAppendedEvent + ) = sessionInstructionsAppended.validity() + + override fun visitSessionThinkingAppended( + sessionThinkingAppended: ThinkingAppendedEvent + ) = sessionThinkingAppended.validity() + + override fun visitSessionCommentaryAppended( + sessionCommentaryAppended: CommentaryAppendedEvent + ) = sessionCommentaryAppended.validity() + + override fun visitSessionInputTranscriptDelta( + sessionInputTranscriptDelta: InputTranscriptDeltaEvent + ) = sessionInputTranscriptDelta.validity() + + override fun visitSessionOutputTranscriptDelta( + sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent + ) = sessionOutputTranscriptDelta.validity() + + override fun visitSessionDelegationCreated( + sessionDelegationCreated: DelegationCreatedEvent + ) = sessionDelegationCreated.validity() + + override fun visitResponseEvent(responseEvent: ResponseEvent) = + responseEvent.validity() + + override fun visitSessionUsageUpdated( + sessionUsageUpdated: SessionUsageUpdatedEvent + ) = sessionUsageUpdated.validity() + + override fun visitSessionClosed(sessionClosed: SessionClosedEvent) = + sessionClosed.validity() + + override fun visitErrorEvent(errorEvent: ErrorEvent) = errorEvent.validity() + + override fun visitInfoEvent(infoEvent: InfoEvent) = infoEvent.validity() + + override fun unknown(json: JsonValue?) = 0 + } + ) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is SidebandServerEvent && + sessionStarted == other.sessionStarted && + sessionUpdated == other.sessionUpdated && + sessionInputAudioMuted == other.sessionInputAudioMuted && + sessionInputAudioUnmuted == other.sessionInputAudioUnmuted && + sessionOutputAudioFixturePlaying == other.sessionOutputAudioFixturePlaying && + sessionOutputAudioFixturePlayed == other.sessionOutputAudioFixturePlayed && + sessionInstructionsAppended == other.sessionInstructionsAppended && + sessionThinkingAppended == other.sessionThinkingAppended && + sessionCommentaryAppended == other.sessionCommentaryAppended && + sessionInputTranscriptDelta == other.sessionInputTranscriptDelta && + sessionOutputTranscriptDelta == other.sessionOutputTranscriptDelta && + sessionDelegationCreated == other.sessionDelegationCreated && + responseEvent == other.responseEvent && + sessionUsageUpdated == other.sessionUsageUpdated && + sessionClosed == other.sessionClosed && + errorEvent == other.errorEvent && + infoEvent == other.infoEvent + } + + override fun hashCode(): Int = + Objects.hash( + sessionStarted, + sessionUpdated, + sessionInputAudioMuted, + sessionInputAudioUnmuted, + sessionOutputAudioFixturePlaying, + sessionOutputAudioFixturePlayed, + sessionInstructionsAppended, + sessionThinkingAppended, + sessionCommentaryAppended, + sessionInputTranscriptDelta, + sessionOutputTranscriptDelta, + sessionDelegationCreated, + responseEvent, + sessionUsageUpdated, + sessionClosed, + errorEvent, + infoEvent, + ) + + override fun toString(): String = + when { + sessionStarted != null -> "SidebandServerEvent{sessionStarted=$sessionStarted}" + sessionUpdated != null -> "SidebandServerEvent{sessionUpdated=$sessionUpdated}" + sessionInputAudioMuted != null -> + "SidebandServerEvent{sessionInputAudioMuted=$sessionInputAudioMuted}" + sessionInputAudioUnmuted != null -> + "SidebandServerEvent{sessionInputAudioUnmuted=$sessionInputAudioUnmuted}" + sessionOutputAudioFixturePlaying != null -> + "SidebandServerEvent{sessionOutputAudioFixturePlaying=$sessionOutputAudioFixturePlaying}" + sessionOutputAudioFixturePlayed != null -> + "SidebandServerEvent{sessionOutputAudioFixturePlayed=$sessionOutputAudioFixturePlayed}" + sessionInstructionsAppended != null -> + "SidebandServerEvent{sessionInstructionsAppended=$sessionInstructionsAppended}" + sessionThinkingAppended != null -> + "SidebandServerEvent{sessionThinkingAppended=$sessionThinkingAppended}" + sessionCommentaryAppended != null -> + "SidebandServerEvent{sessionCommentaryAppended=$sessionCommentaryAppended}" + sessionInputTranscriptDelta != null -> + "SidebandServerEvent{sessionInputTranscriptDelta=$sessionInputTranscriptDelta}" + sessionOutputTranscriptDelta != null -> + "SidebandServerEvent{sessionOutputTranscriptDelta=$sessionOutputTranscriptDelta}" + sessionDelegationCreated != null -> + "SidebandServerEvent{sessionDelegationCreated=$sessionDelegationCreated}" + responseEvent != null -> "SidebandServerEvent{responseEvent=$responseEvent}" + sessionUsageUpdated != null -> + "SidebandServerEvent{sessionUsageUpdated=$sessionUsageUpdated}" + sessionClosed != null -> "SidebandServerEvent{sessionClosed=$sessionClosed}" + errorEvent != null -> "SidebandServerEvent{errorEvent=$errorEvent}" + infoEvent != null -> "SidebandServerEvent{infoEvent=$infoEvent}" + _json != null -> "SidebandServerEvent{_unknown=$_json}" + else -> throw IllegalStateException("Invalid SidebandServerEvent") + } + + companion object { + + /** + * Returned when a Live session has started. Contains the resolved session configuration, + * including server defaults. + */ + @JvmStatic + fun ofSessionStarted(sessionStarted: SessionStartedEvent) = + SidebandServerEvent(sessionStarted = sessionStarted) + + /** + * Returned when a Live session update is accepted. Contains the resolved session + * configuration after the update. + */ + @JvmStatic + fun ofSessionUpdated(sessionUpdated: SessionUpdatedEvent) = + SidebandServerEvent(sessionUpdated = sessionUpdated) + + /** + * Returned when a session.input_audio.mute command is accepted. Input audio is no longer + * sent to the model; sideband audio reflection continues. + */ + @JvmStatic + fun ofSessionInputAudioMuted(sessionInputAudioMuted: InputAudioMutedEvent) = + SidebandServerEvent(sessionInputAudioMuted = sessionInputAudioMuted) + + /** + * Returned when a session.input_audio.unmute command is accepted. Input audio is sent to + * the model again. + */ + @JvmStatic + fun ofSessionInputAudioUnmuted(sessionInputAudioUnmuted: InputAudioUnmutedEvent) = + SidebandServerEvent(sessionInputAudioUnmuted = sessionInputAudioUnmuted) + + /** + * The existing playback path has accepted the validated audio fixture. This acknowledgment + * does not identify when the listener receives the first sample. The optional + * client_event_id echoes the play command. + */ + @JvmStatic + fun ofSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ) = SidebandServerEvent(sessionOutputAudioFixturePlaying = sessionOutputAudioFixturePlaying) + + /** + * The complete audio fixture has drained from the server mixer and normal inference has + * resumed. Transport buffers may still contain audio; this event does not prove + * listener-side completion. The optional client_event_id echoes the play command. This + * event is not emitted for canceled or failed playback. + */ + @JvmStatic + fun ofSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ) = SidebandServerEvent(sessionOutputAudioFixturePlayed = sessionOutputAudioFixturePlayed) + + /** + * Returned when a session.instructions.append command is accepted into the Live session + * timeline. Acknowledges the appended instructions without guaranteeing that the model has + * acted on them. + */ + @JvmStatic + fun ofSessionInstructionsAppended(sessionInstructionsAppended: InstructionsAppendedEvent) = + SidebandServerEvent(sessionInstructionsAppended = sessionInstructionsAppended) + + /** + * Returned when a session.thinking.append command is accepted into the Live session + * timeline. Acknowledges the added reasoning context without guaranteeing any spoken + * output. + */ + @JvmStatic + fun ofSessionThinkingAppended(sessionThinkingAppended: ThinkingAppendedEvent) = + SidebandServerEvent(sessionThinkingAppended = sessionThinkingAppended) + + /** + * Returned when a session.commentary.append command is accepted into the Live session + * timeline. Acknowledges the added commentary without guaranteeing exact wording or + * completed audio playback. + */ + @JvmStatic + fun ofSessionCommentaryAppended(sessionCommentaryAppended: CommentaryAppendedEvent) = + SidebandServerEvent(sessionCommentaryAppended = sessionCommentaryAppended) + + /** + * A transcript fragment for user input audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done + * event. + */ + @JvmStatic + fun ofSessionInputTranscriptDelta(sessionInputTranscriptDelta: InputTranscriptDeltaEvent) = + SidebandServerEvent(sessionInputTranscriptDelta = sessionInputTranscriptDelta) + + /** + * A transcript fragment for assistant output audio in the Live session. Accumulate + * fragments in delivery order; these events do not define complete turns or include a + * transcript-done event. + */ + @JvmStatic + fun ofSessionOutputTranscriptDelta( + sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent + ) = SidebandServerEvent(sessionOutputTranscriptDelta = sessionOutputTranscriptDelta) + + /** + * Returned when the Live model delegates work to your application or a Responses backend. + * Contains delegation metadata and the position on the session timeline where the work was + * delegated. + */ + @JvmStatic + fun ofSessionDelegationCreated(sessionDelegationCreated: DelegationCreatedEvent) = + SidebandServerEvent(sessionDelegationCreated = sessionDelegationCreated) + + /** + * A streaming Responses API event from a backend delegated to by the Live session. Use the + * outer delegation_id to associate the nested stream with its Live delegation. + */ + @JvmStatic + fun ofResponseEvent(responseEvent: ResponseEvent) = + SidebandServerEvent(responseEvent = responseEvent) + + /** + * Reports cumulative Live audio usage and, when available, the most recent context-window + * usage. Delegated Responses token usage is reported separately in response.event events. + */ + @JvmStatic + fun ofSessionUsageUpdated(sessionUsageUpdated: SessionUsageUpdatedEvent) = + SidebandServerEvent(sessionUsageUpdated = sessionUsageUpdated) + + /** + * Returned after the Live session finishes finalizing, with the close reason, final session + * snapshot, and cumulative audio usage. A connection closing without this event does not + * confirm successful finalization. + */ + @JvmStatic + fun ofSessionClosed(sessionClosed: SessionClosedEvent) = + SidebandServerEvent(sessionClosed = sessionClosed) + + /** + * Reports an error in the Live session, such as an invalid client command. Use + * error.client_event_id, when present, to identify the command that caused the error. + */ + @JvmStatic + fun ofErrorEvent(errorEvent: ErrorEvent) = SidebandServerEvent(errorEvent = errorEvent) + + /** + * An informational notice about the Live session, such as the event permissions applied to + * a frontend data channel. + */ + @JvmStatic + fun ofInfoEvent(infoEvent: InfoEvent) = SidebandServerEvent(infoEvent = infoEvent) + } + + /** + * An interface that defines how to map each variant of [SidebandServerEvent] to a value of type + * [T]. + */ + interface Visitor { + + /** + * Returned when a Live session has started. Contains the resolved session configuration, + * including server defaults. + */ + fun visitSessionStarted(sessionStarted: SessionStartedEvent): T = + unknown(JsonValue.from(sessionStarted)) + + /** + * Returned when a Live session update is accepted. Contains the resolved session + * configuration after the update. + */ + fun visitSessionUpdated(sessionUpdated: SessionUpdatedEvent): T = + unknown(JsonValue.from(sessionUpdated)) + + /** + * Returned when a session.input_audio.mute command is accepted. Input audio is no longer + * sent to the model; sideband audio reflection continues. + */ + fun visitSessionInputAudioMuted(sessionInputAudioMuted: InputAudioMutedEvent): T = + unknown(JsonValue.from(sessionInputAudioMuted)) + + /** + * Returned when a session.input_audio.unmute command is accepted. Input audio is sent to + * the model again. + */ + fun visitSessionInputAudioUnmuted(sessionInputAudioUnmuted: InputAudioUnmutedEvent): T = + unknown(JsonValue.from(sessionInputAudioUnmuted)) + + /** + * The existing playback path has accepted the validated audio fixture. This acknowledgment + * does not identify when the listener receives the first sample. The optional + * client_event_id echoes the play command. + */ + fun visitSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ): T = unknown(JsonValue.from(sessionOutputAudioFixturePlaying)) + + /** + * The complete audio fixture has drained from the server mixer and normal inference has + * resumed. Transport buffers may still contain audio; this event does not prove + * listener-side completion. The optional client_event_id echoes the play command. This + * event is not emitted for canceled or failed playback. + */ + fun visitSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ): T = unknown(JsonValue.from(sessionOutputAudioFixturePlayed)) + + /** + * Returned when a session.instructions.append command is accepted into the Live session + * timeline. Acknowledges the appended instructions without guaranteeing that the model has + * acted on them. + */ + fun visitSessionInstructionsAppended( + sessionInstructionsAppended: InstructionsAppendedEvent + ): T = unknown(JsonValue.from(sessionInstructionsAppended)) + + /** + * Returned when a session.thinking.append command is accepted into the Live session + * timeline. Acknowledges the added reasoning context without guaranteeing any spoken + * output. + */ + fun visitSessionThinkingAppended(sessionThinkingAppended: ThinkingAppendedEvent): T = + unknown(JsonValue.from(sessionThinkingAppended)) + + /** + * Returned when a session.commentary.append command is accepted into the Live session + * timeline. Acknowledges the added commentary without guaranteeing exact wording or + * completed audio playback. + */ + fun visitSessionCommentaryAppended(sessionCommentaryAppended: CommentaryAppendedEvent): T = + unknown(JsonValue.from(sessionCommentaryAppended)) + + /** + * A transcript fragment for user input audio in the Live session. Accumulate fragments in + * delivery order; these events do not define complete turns or include a transcript-done + * event. + */ + fun visitSessionInputTranscriptDelta( + sessionInputTranscriptDelta: InputTranscriptDeltaEvent + ): T = unknown(JsonValue.from(sessionInputTranscriptDelta)) + + /** + * A transcript fragment for assistant output audio in the Live session. Accumulate + * fragments in delivery order; these events do not define complete turns or include a + * transcript-done event. + */ + fun visitSessionOutputTranscriptDelta( + sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent + ): T = unknown(JsonValue.from(sessionOutputTranscriptDelta)) + + /** + * Returned when the Live model delegates work to your application or a Responses backend. + * Contains delegation metadata and the position on the session timeline where the work was + * delegated. + */ + fun visitSessionDelegationCreated(sessionDelegationCreated: DelegationCreatedEvent): T = + unknown(JsonValue.from(sessionDelegationCreated)) + + /** + * A streaming Responses API event from a backend delegated to by the Live session. Use the + * outer delegation_id to associate the nested stream with its Live delegation. + */ + fun visitResponseEvent(responseEvent: ResponseEvent): T = + unknown(JsonValue.from(responseEvent)) + + /** + * Reports cumulative Live audio usage and, when available, the most recent context-window + * usage. Delegated Responses token usage is reported separately in response.event events. + */ + fun visitSessionUsageUpdated(sessionUsageUpdated: SessionUsageUpdatedEvent): T = + unknown(JsonValue.from(sessionUsageUpdated)) + + /** + * Returned after the Live session finishes finalizing, with the close reason, final session + * snapshot, and cumulative audio usage. A connection closing without this event does not + * confirm successful finalization. + */ + fun visitSessionClosed(sessionClosed: SessionClosedEvent): T = + unknown(JsonValue.from(sessionClosed)) + + /** + * Reports an error in the Live session, such as an invalid client command. Use + * error.client_event_id, when present, to identify the command that caused the error. + */ + fun visitErrorEvent(errorEvent: ErrorEvent): T = unknown(JsonValue.from(errorEvent)) + + /** + * An informational notice about the Live session, such as the event permissions applied to + * a frontend data channel. + */ + fun visitInfoEvent(infoEvent: InfoEvent): T = unknown(JsonValue.from(infoEvent)) + + /** + * Maps an unknown variant of [SidebandServerEvent] to a value of type [T]. + * + * An instance of [SidebandServerEvent] 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. + * + * Recognized variants also reach this method when their visit method is not overridden. + * This allows existing visitors to handle variants added by newer SDK versions. + * + * @throws OpenAIInvalidDataException in the default implementation. + */ + fun unknown(json: JsonValue?): T { + throw OpenAIInvalidDataException("Unknown SidebandServerEvent") + } + } + + internal class Deserializer : + BaseDeserializer(SidebandServerEvent::class) { + + override fun ObjectCodec.deserialize(node: JsonNode): SidebandServerEvent { + val json = JsonValue.fromJsonNode(node) + val type = json.asObject().getOrNull()?.get("type")?.asString()?.getOrNull() + + when (type) { + "session.started" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionStarted = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.updated" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionUpdated = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.input_audio.muted" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionInputAudioMuted = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.input_audio.unmuted" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionInputAudioUnmuted = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.output_audio.fixture.playing" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { + SidebandServerEvent(sessionOutputAudioFixturePlaying = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.output_audio.fixture.played" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { + SidebandServerEvent(sessionOutputAudioFixturePlayed = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.instructions.appended" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionInstructionsAppended = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.thinking.appended" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionThinkingAppended = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.commentary.appended" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionCommentaryAppended = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.input_transcript.delta" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionInputTranscriptDelta = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.output_transcript.delta" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionOutputTranscriptDelta = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.delegation.created" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionDelegationCreated = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "response.event" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(responseEvent = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.usage.updated" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionUsageUpdated = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "session.closed" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(sessionClosed = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "error" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(errorEvent = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + "info" -> { + return tryDeserialize(node, jacksonTypeRef())?.let { + SidebandServerEvent(infoEvent = it, _json = json) + } ?: SidebandServerEvent(_json = json) + } + } + + return SidebandServerEvent(_json = json) + } + } + + internal class Serializer : BaseSerializer(SidebandServerEvent::class) { + + override fun serialize( + value: SidebandServerEvent, + generator: JsonGenerator, + provider: SerializerProvider, + ) { + when { + value.sessionStarted != null -> generator.writeObject(value.sessionStarted) + value.sessionUpdated != null -> generator.writeObject(value.sessionUpdated) + value.sessionInputAudioMuted != null -> + generator.writeObject(value.sessionInputAudioMuted) + value.sessionInputAudioUnmuted != null -> + generator.writeObject(value.sessionInputAudioUnmuted) + value.sessionOutputAudioFixturePlaying != null -> + generator.writeObject(value.sessionOutputAudioFixturePlaying) + value.sessionOutputAudioFixturePlayed != null -> + generator.writeObject(value.sessionOutputAudioFixturePlayed) + value.sessionInstructionsAppended != null -> + generator.writeObject(value.sessionInstructionsAppended) + value.sessionThinkingAppended != null -> + generator.writeObject(value.sessionThinkingAppended) + value.sessionCommentaryAppended != null -> + generator.writeObject(value.sessionCommentaryAppended) + value.sessionInputTranscriptDelta != null -> + generator.writeObject(value.sessionInputTranscriptDelta) + value.sessionOutputTranscriptDelta != null -> + generator.writeObject(value.sessionOutputTranscriptDelta) + value.sessionDelegationCreated != null -> + generator.writeObject(value.sessionDelegationCreated) + value.responseEvent != null -> generator.writeObject(value.responseEvent) + value.sessionUsageUpdated != null -> + generator.writeObject(value.sessionUsageUpdated) + value.sessionClosed != null -> generator.writeObject(value.sessionClosed) + value.errorEvent != null -> generator.writeObject(value.errorEvent) + value.infoEvent != null -> generator.writeObject(value.infoEvent) + value._json != null -> generator.writeObject(value._json) + else -> throw IllegalStateException("Invalid SidebandServerEvent") + } + } + } +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandWebSocketBinding.kt new file mode 100644 index 000000000..06348c5b0 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandWebSocketBinding.kt @@ -0,0 +1,22 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.sideband + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object SidebandWebSocketBinding { + fun create( + `sessionId`: String + ): WebSocketBinding< + com.openai.models.live.sideband.SidebandClientEvent, + com.openai.models.live.sideband.SidebandServerEvent, + > = + WebSocketBinding( + listOf("live", "sessions", `sessionId`.toString(), "attach"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.live.sideband.SidebandServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationServerEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationServerEvent.kt index 4b9c72166..e37a1ae5f 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationServerEvent.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationServerEvent.kt @@ -434,23 +434,26 @@ private constructor( * errors are recoverable and the session will stay open, we recommend to implementors to * monitor and log error messages by default. */ - fun visitError(error: RealtimeErrorEvent): T + fun visitError(error: RealtimeErrorEvent): T = unknown(JsonValue.from(error)) /** * Returned when a translation session is created. Emitted automatically when a new * connection is established as the first server event. This event contains the default * translation session configuration. */ - fun visitSessionCreated(sessionCreated: RealtimeTranslationSessionCreatedEvent): T + fun visitSessionCreated(sessionCreated: RealtimeTranslationSessionCreatedEvent): T = + unknown(JsonValue.from(sessionCreated)) /** * Returned when a translation session is updated with a `session.update` event, unless * there is an error. */ - fun visitSessionUpdated(sessionUpdated: RealtimeTranslationSessionUpdatedEvent): T + fun visitSessionUpdated(sessionUpdated: RealtimeTranslationSessionUpdatedEvent): T = + unknown(JsonValue.from(sessionUpdated)) /** Returned when a realtime translation session is closed. */ - fun visitSessionClosed(sessionClosed: RealtimeTranslationSessionClosedEvent): T + fun visitSessionClosed(sessionClosed: RealtimeTranslationSessionClosedEvent): T = + unknown(JsonValue.from(sessionClosed)) /** * Returned when optional source-language transcript text is available. This event is @@ -461,7 +464,7 @@ private constructor( */ fun visitSessionInputTranscriptDelta( sessionInputTranscriptDelta: RealtimeTranslationInputTranscriptDeltaEvent - ): T + ): T = unknown(JsonValue.from(sessionInputTranscriptDelta)) /** * Returned when translated transcript text is available. @@ -471,7 +474,7 @@ private constructor( */ fun visitSessionOutputTranscriptDelta( sessionOutputTranscriptDelta: RealtimeTranslationOutputTranscriptDeltaEvent - ): T + ): T = unknown(JsonValue.from(sessionOutputTranscriptDelta)) /** * Returned when translated output audio is available. The `delta` contains a PCM16 audio @@ -480,7 +483,7 @@ private constructor( */ fun visitSessionOutputAudioDelta( sessionOutputAudioDelta: RealtimeTranslationOutputAudioDeltaEvent - ): T + ): T = unknown(JsonValue.from(sessionOutputAudioDelta)) /** * Maps an unknown variant of [RealtimeTranslationServerEvent] to a value of type [T]. @@ -490,6 +493,9 @@ private constructor( * on an older version than the API, then the API may respond with new variants that the SDK * is unaware of. * + * Recognized variants also reach this method when their visit method is not overridden. + * This allows existing visitors to handle variants added by newer SDK versions. + * * @throws OpenAIInvalidDataException in the default implementation. */ fun unknown(json: JsonValue?): T { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeWebSocketBinding.kt new file mode 100644 index 000000000..1d956c663 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeWebSocketBinding.kt @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.realtime + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object RealtimeWebSocketBinding { + fun create(): + WebSocketBinding< + com.openai.models.realtime.RealtimeClientEvent, + com.openai.models.realtime.RealtimeServerEvent, + > = + WebSocketBinding( + listOf("realtime"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.realtime.RealtimeServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/TranslationWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/TranslationWebSocketBinding.kt new file mode 100644 index 000000000..a39cf5310 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/TranslationWebSocketBinding.kt @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.realtime.translations + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object TranslationWebSocketBinding { + fun create(): + WebSocketBinding< + com.openai.models.realtime.RealtimeTranslationClientEvent, + com.openai.models.realtime.RealtimeTranslationServerEvent, + > = + WebSocketBinding( + listOf("realtime", "translations"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.realtime.RealtimeTranslationServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt index c4efe6ec6..e118198d5 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt @@ -2822,9 +2822,9 @@ private constructor( */ fun addContextManagement(contextManagement: ContextManagement) = apply { this.contextManagement = - (this.contextManagement ?: JsonField.of(mutableListOf())).also { - checkKnown("contextManagement", it).add(contextManagement) - } + (this.contextManagement?.takeUnless { it.isMissing() } + ?: JsonField.of(mutableListOf())) + .also { checkKnown("contextManagement", it).add(contextManagement) } } /** @@ -2902,9 +2902,8 @@ private constructor( */ fun addInclude(include: ResponseIncludable) = apply { this.include = - (this.include ?: JsonField.of(mutableListOf())).also { - checkKnown("include", it).add(include) - } + (this.include?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("include", it).add(include) } } /** @@ -3517,7 +3516,7 @@ private constructor( */ fun addTool(tool: Tool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseWebSocketBinding.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseWebSocketBinding.kt new file mode 100644 index 000000000..0494dfef0 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseWebSocketBinding.kt @@ -0,0 +1,21 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.responses + +import com.openai.core.SecurityOptions +import com.openai.core.http.WebSocketBinding + +/** Endpoint metadata consumed by SDK-owned WebSocket helpers. */ +internal object ResponseWebSocketBinding { + fun create(): + WebSocketBinding< + com.openai.models.responses.ResponsesClientEvent, + com.openai.models.responses.ResponsesServerEvent, + > = + WebSocketBinding( + listOf("responses"), + SecurityOptions.builder().bearerAuth(true).build(), + com.openai.models.responses.ResponsesServerEvent::class.java, + { it.validate() }, + ) +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountParams.kt index a3d82ed0d..d8b9d169e 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountParams.kt @@ -1515,7 +1515,7 @@ private constructor( */ fun addTool(tool: Tool) = apply { tools = - (tools ?: JsonField.of(mutableListOf())).also { + (tools?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("tools", it).add(tool) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCompleteParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCompleteParams.kt index 92a9eda0a..52df1fbd3 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCompleteParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCompleteParams.kt @@ -421,7 +421,7 @@ private constructor( */ fun addPartId(partId: String) = apply { partIds = - (partIds ?: JsonField.of(mutableListOf())).also { + (partIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("partIds", it).add(partId) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreCreateParams.kt index cae13eda3..b37ee7dd5 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreCreateParams.kt @@ -722,7 +722,7 @@ private constructor( */ fun addFileId(fileId: String) = apply { fileIds = - (fileIds ?: JsonField.of(mutableListOf())).also { + (fileIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("fileIds", it).add(fileId) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchCreateParams.kt index 3ef876989..e76aff30e 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchCreateParams.kt @@ -659,7 +659,7 @@ private constructor( */ fun addFileId(fileId: String) = apply { fileIds = - (fileIds ?: JsonField.of(mutableListOf())).also { + (fileIds?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("fileIds", it).add(fileId) } } @@ -692,7 +692,7 @@ private constructor( */ fun addFile(file: File) = apply { files = - (files ?: JsonField.of(mutableListOf())).also { + (files?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())).also { checkKnown("files", it).add(file) } } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookCreateParams.kt index c057e4640..959871a5a 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookCreateParams.kt @@ -442,9 +442,8 @@ private constructor( */ fun addEventType(eventType: EventType) = apply { eventTypes = - (eventTypes ?: JsonField.of(mutableListOf())).also { - checkKnown("eventTypes", it).add(eventType) - } + (eventTypes?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("eventTypes", it).add(eventType) } } /** A human-readable name for the webhook endpoint. */ diff --git a/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookUpdateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookUpdateParams.kt index 7209a5417..07d58280c 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookUpdateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookUpdateParams.kt @@ -437,9 +437,8 @@ private constructor( */ fun addEventType(eventType: EventType) = apply { eventTypes = - (eventTypes ?: JsonField.of(mutableListOf())).also { - checkKnown("eventTypes", it).add(eventType) - } + (eventTypes?.takeUnless { it.isMissing() } ?: JsonField.of(mutableListOf())) + .also { checkKnown("eventTypes", it).add(eventType) } } /** A new human-readable name for the webhook endpoint. */ diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandClientEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandClientEventTest.kt new file mode 100644 index 000000000..eb457e7cc --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandClientEventTest.kt @@ -0,0 +1,590 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.sideband + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonValue +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +import com.openai.models.live.CommentaryAppendEvent +import com.openai.models.live.FunctionTool +import com.openai.models.live.InputAudioMuteEvent +import com.openai.models.live.InputAudioUnmuteEvent +import com.openai.models.live.InstructionsAppendEvent +import com.openai.models.live.OutputAudioFixturePlayEvent +import com.openai.models.live.ResponseCreateEvent +import com.openai.models.live.ResponseItemCreateEvent +import com.openai.models.live.ResponsesDelegationUpdateConfig +import com.openai.models.live.SessionCloseEvent +import com.openai.models.live.SessionUpdateConfig +import com.openai.models.live.SessionUpdateEvent +import com.openai.models.live.ThinkingAppendEvent +import com.openai.models.responses.EasyInputMessage +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class SidebandClientEventTest { + + @Test + fun ofSessionUpdate() { + val sessionUpdate = + SessionUpdateEvent.builder() + .session( + SessionUpdateConfig.builder() + .delegation( + SessionUpdateConfig.Delegation.Responses.builder() + .responses( + ResponsesDelegationUpdateConfig.builder() + .instructions( + "Check restaurant availability. Ask before confirming a booking." + ) + .maxOutputTokens(1024L) + .model("model") + .parallelToolCalls(true) + .reasoning( + ResponsesDelegationUpdateConfig.Reasoning.builder() + .effort( + ResponsesDelegationUpdateConfig.Reasoning.Effort + .NONE + ) + .summary( + ResponsesDelegationUpdateConfig.Reasoning + .Summary + .CONCISE + ) + .build() + ) + .serviceTier( + ResponsesDelegationUpdateConfig.ServiceTier.AUTO + ) + .text( + ResponsesDelegationUpdateConfig.Text.builder() + .verbosity( + ResponsesDelegationUpdateConfig.Text.Verbosity + .LOW + ) + .build() + ) + .toolChoice( + ResponsesDelegationUpdateConfig.ToolChoice + .LiveToolChoiceEnum + .AUTO + ) + .addTool( + FunctionTool.builder() + .name("name") + .description("description") + .parameters( + FunctionTool.Parameters.builder() + .putAdditionalProperty( + "foo", + JsonValue.from("bar"), + ) + .build() + ) + .strict(true) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + .eventId("evt_update_001") + .build() + + val sidebandClientEvent = SidebandClientEvent.ofSessionUpdate(sessionUpdate) + + assertThat(sidebandClientEvent.sessionUpdate()).contains(sessionUpdate) + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionUpdateRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionUpdate( + SessionUpdateEvent.builder() + .session( + SessionUpdateConfig.builder() + .delegation( + SessionUpdateConfig.Delegation.Responses.builder() + .responses( + ResponsesDelegationUpdateConfig.builder() + .instructions( + "Check restaurant availability. Ask before confirming a booking." + ) + .maxOutputTokens(1024L) + .model("model") + .parallelToolCalls(true) + .reasoning( + ResponsesDelegationUpdateConfig.Reasoning.builder() + .effort( + ResponsesDelegationUpdateConfig.Reasoning + .Effort + .NONE + ) + .summary( + ResponsesDelegationUpdateConfig.Reasoning + .Summary + .CONCISE + ) + .build() + ) + .serviceTier( + ResponsesDelegationUpdateConfig.ServiceTier.AUTO + ) + .text( + ResponsesDelegationUpdateConfig.Text.builder() + .verbosity( + ResponsesDelegationUpdateConfig.Text + .Verbosity + .LOW + ) + .build() + ) + .toolChoice( + ResponsesDelegationUpdateConfig.ToolChoice + .LiveToolChoiceEnum + .AUTO + ) + .addTool( + FunctionTool.builder() + .name("name") + .description("description") + .parameters( + FunctionTool.Parameters.builder() + .putAdditionalProperty( + "foo", + JsonValue.from("bar"), + ) + .build() + ) + .strict(true) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + .eventId("evt_update_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionInputAudioMute() { + val sessionInputAudioMute = InputAudioMuteEvent.builder().eventId("evt_mute_001").build() + + val sidebandClientEvent = SidebandClientEvent.ofSessionInputAudioMute(sessionInputAudioMute) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).contains(sessionInputAudioMute) + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionInputAudioMuteRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionInputAudioMute( + InputAudioMuteEvent.builder().eventId("evt_mute_001").build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionInputAudioUnmute() { + val sessionInputAudioUnmute = + InputAudioUnmuteEvent.builder().eventId("evt_unmute_001").build() + + val sidebandClientEvent = + SidebandClientEvent.ofSessionInputAudioUnmute(sessionInputAudioUnmute) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).contains(sessionInputAudioUnmute) + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionInputAudioUnmuteRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionInputAudioUnmute( + InputAudioUnmuteEvent.builder().eventId("evt_unmute_001").build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionOutputAudioFixturePlay() { + val sessionOutputAudioFixturePlay = + OutputAudioFixturePlayEvent.builder().audio("AAAAAA==").eventId("evt_play_001").build() + + val sidebandClientEvent = + SidebandClientEvent.ofSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()) + .contains(sessionOutputAudioFixturePlay) + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionOutputAudioFixturePlay( + OutputAudioFixturePlayEvent.builder() + .audio("AAAAAA==") + .eventId("evt_play_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionInstructionsAppend() { + val sessionInstructionsAppend = + InstructionsAppendEvent.builder() + .content("The caller prefers outdoor seating.") + .delegationId(null) + .eventId("evt_instructions_001") + .build() + + val sidebandClientEvent = + SidebandClientEvent.ofSessionInstructionsAppend(sessionInstructionsAppend) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()) + .contains(sessionInstructionsAppend) + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionInstructionsAppendRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionInstructionsAppend( + InstructionsAppendEvent.builder() + .content("The caller prefers outdoor seating.") + .delegationId(null) + .eventId("evt_instructions_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionThinkingAppend() { + val sessionThinkingAppend = + ThinkingAppendEvent.builder() + .content("Checking availability for two guests at 7 PM.") + .delegationId("del_abc123") + .eventId("evt_thinking_001") + .build() + + val sidebandClientEvent = SidebandClientEvent.ofSessionThinkingAppend(sessionThinkingAppend) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).contains(sessionThinkingAppend) + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionThinkingAppendRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionThinkingAppend( + ThinkingAppendEvent.builder() + .content("Checking availability for two guests at 7 PM.") + .delegationId("del_abc123") + .eventId("evt_thinking_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionCommentaryAppend() { + val sessionCommentaryAppend = + CommentaryAppendEvent.builder() + .content("There is an outdoor table for two at 7 PM. Ask whether to reserve it.") + .delegationId("del_abc123") + .eventId("evt_commentary_001") + .build() + + val sidebandClientEvent = + SidebandClientEvent.ofSessionCommentaryAppend(sessionCommentaryAppend) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).contains(sessionCommentaryAppend) + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionCommentaryAppendRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionCommentaryAppend( + CommentaryAppendEvent.builder() + .content( + "There is an outdoor table for two at 7 PM. Ask whether to reserve it." + ) + .delegationId("del_abc123") + .eventId("evt_commentary_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofResponseItemCreate() { + val responseItemCreate = + ResponseItemCreateEvent.builder() + .item( + EasyInputMessage.builder() + .content("string") + .role(EasyInputMessage.Role.USER) + .phase(EasyInputMessage.Phase.COMMENTARY) + .type(EasyInputMessage.Type.MESSAGE) + .build() + ) + .eventId("evt_item_001") + .build() + + val sidebandClientEvent = SidebandClientEvent.ofResponseItemCreate(responseItemCreate) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).contains(responseItemCreate) + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofResponseItemCreateRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofResponseItemCreate( + ResponseItemCreateEvent.builder() + .item( + EasyInputMessage.builder() + .content("string") + .role(EasyInputMessage.Role.USER) + .phase(EasyInputMessage.Phase.COMMENTARY) + .type(EasyInputMessage.Type.MESSAGE) + .build() + ) + .eventId("evt_item_001") + .build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofResponseCreate() { + val responseCreate = ResponseCreateEvent.builder().eventId("evt_response_001").build() + + val sidebandClientEvent = SidebandClientEvent.ofResponseCreate(responseCreate) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).contains(responseCreate) + assertThat(sidebandClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofResponseCreateRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofResponseCreate( + ResponseCreateEvent.builder().eventId("evt_response_001").build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + @Test + fun ofSessionClose() { + val sessionClose = SessionCloseEvent.builder().eventId("evt_close_001").build() + + val sidebandClientEvent = SidebandClientEvent.ofSessionClose(sessionClose) + + assertThat(sidebandClientEvent.sessionUpdate()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioMute()).isEmpty + assertThat(sidebandClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(sidebandClientEvent.sessionOutputAudioFixturePlay()).isEmpty + assertThat(sidebandClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(sidebandClientEvent.sessionThinkingAppend()).isEmpty + assertThat(sidebandClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(sidebandClientEvent.responseItemCreate()).isEmpty + assertThat(sidebandClientEvent.responseCreate()).isEmpty + assertThat(sidebandClientEvent.sessionClose()).contains(sessionClose) + } + + @Test + fun ofSessionCloseRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandClientEvent = + SidebandClientEvent.ofSessionClose( + SessionCloseEvent.builder().eventId("evt_close_001").build() + ) + + val roundtrippedSidebandClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandClientEvent).isEqualTo(sidebandClientEvent) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val sidebandClientEvent = + jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { sidebandClientEvent.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandServerEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandServerEventTest.kt new file mode 100644 index 000000000..61ac7fae1 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandServerEventTest.kt @@ -0,0 +1,1274 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live.sideband + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonValue +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +import com.openai.models.live.AudioFormat +import com.openai.models.live.BuiltInVoice +import com.openai.models.live.ClientConfig +import com.openai.models.live.ClientDelegation +import com.openai.models.live.CommentaryAppendedEvent +import com.openai.models.live.DataChannelConfig +import com.openai.models.live.DelegationCreatedEvent +import com.openai.models.live.Error +import com.openai.models.live.ErrorEvent +import com.openai.models.live.InfoEvent +import com.openai.models.live.InputAudioMutedEvent +import com.openai.models.live.InputAudioUnmutedEvent +import com.openai.models.live.InputTranscriptDeltaEvent +import com.openai.models.live.InstructionsAppendedEvent +import com.openai.models.live.OutputAudioFixturePlayedEvent +import com.openai.models.live.OutputAudioFixturePlayingEvent +import com.openai.models.live.OutputTranscriptDeltaEvent +import com.openai.models.live.ResponseEvent +import com.openai.models.live.ResponsesDelegationConfig +import com.openai.models.live.SessionClosedEvent +import com.openai.models.live.SessionResource +import com.openai.models.live.SessionStartedEvent +import com.openai.models.live.SessionUpdatedEvent +import com.openai.models.live.SessionUsage +import com.openai.models.live.SessionUsageUpdatedEvent +import com.openai.models.live.ThinkingAppendedEvent +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class SidebandServerEventTest { + + @Test + fun ofSessionStarted() { + val sessionStarted = + SessionStartedEvent.builder() + .eventId("evt_started_001") + .session( + SessionResource.builder() + .id("live_abc123") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .delegation(ClientDelegation.builder().build()) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .clientEventId("evt_start_001") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofSessionStarted(sessionStarted) + + assertThat(sidebandServerEvent.sessionStarted()).contains(sessionStarted) + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionStartedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionStarted( + SessionStartedEvent.builder() + .eventId("evt_started_001") + .session( + SessionResource.builder() + .id("live_abc123") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .delegation(ClientDelegation.builder().build()) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .clientEventId("evt_start_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionUpdated() { + val sessionUpdated = + SessionUpdatedEvent.builder() + .eventId("evt_updated_001") + .session( + SessionResource.builder() + .id("live_def456") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .responsesDelegation( + ResponsesDelegationConfig.builder() + .model("gpt-6-astra") + .instructions( + "Check restaurant availability. Ask before confirming a booking." + ) + .maxOutputTokens(1024L) + .parallelToolCalls(true) + .reasoning( + ResponsesDelegationConfig.Reasoning.builder() + .effort(ResponsesDelegationConfig.Reasoning.Effort.NONE) + .summary( + ResponsesDelegationConfig.Reasoning.Summary.CONCISE + ) + .build() + ) + .serviceTier(ResponsesDelegationConfig.ServiceTier.AUTO) + .text( + ResponsesDelegationConfig.Text.builder() + .verbosity(ResponsesDelegationConfig.Text.Verbosity.LOW) + .build() + ) + .toolChoice( + ResponsesDelegationConfig.ToolChoice.LiveToolChoiceEnum.AUTO + ) + .tools(listOf()) + .build() + ) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .clientEventId("evt_update_001") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofSessionUpdated(sessionUpdated) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).contains(sessionUpdated) + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionUpdatedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionUpdated( + SessionUpdatedEvent.builder() + .eventId("evt_updated_001") + .session( + SessionResource.builder() + .id("live_def456") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .responsesDelegation( + ResponsesDelegationConfig.builder() + .model("gpt-6-astra") + .instructions( + "Check restaurant availability. Ask before confirming a booking." + ) + .maxOutputTokens(1024L) + .parallelToolCalls(true) + .reasoning( + ResponsesDelegationConfig.Reasoning.builder() + .effort(ResponsesDelegationConfig.Reasoning.Effort.NONE) + .summary( + ResponsesDelegationConfig.Reasoning.Summary.CONCISE + ) + .build() + ) + .serviceTier(ResponsesDelegationConfig.ServiceTier.AUTO) + .text( + ResponsesDelegationConfig.Text.builder() + .verbosity(ResponsesDelegationConfig.Text.Verbosity.LOW) + .build() + ) + .toolChoice( + ResponsesDelegationConfig.ToolChoice.LiveToolChoiceEnum.AUTO + ) + .tools(listOf()) + .build() + ) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .clientEventId("evt_update_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionInputAudioMuted() { + val sessionInputAudioMuted = + InputAudioMutedEvent.builder() + .eventId("evt_muted_001") + .clientEventId("evt_mute_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputAudioMuted(sessionInputAudioMuted) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).contains(sessionInputAudioMuted) + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionInputAudioMutedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputAudioMuted( + InputAudioMutedEvent.builder() + .eventId("evt_muted_001") + .clientEventId("evt_mute_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionInputAudioUnmuted() { + val sessionInputAudioUnmuted = + InputAudioUnmutedEvent.builder() + .eventId("evt_unmuted_001") + .clientEventId("evt_unmute_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputAudioUnmuted(sessionInputAudioUnmuted) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()) + .contains(sessionInputAudioUnmuted) + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionInputAudioUnmutedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputAudioUnmuted( + InputAudioUnmutedEvent.builder() + .eventId("evt_unmuted_001") + .clientEventId("evt_unmute_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionOutputAudioFixturePlaying() { + val sessionOutputAudioFixturePlaying = + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputAudioFixturePlaying(sessionOutputAudioFixturePlaying) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()) + .contains(sessionOutputAudioFixturePlaying) + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayingRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputAudioFixturePlaying( + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionOutputAudioFixturePlayed() { + val sessionOutputAudioFixturePlayed = + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputAudioFixturePlayed(sessionOutputAudioFixturePlayed) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()) + .contains(sessionOutputAudioFixturePlayed) + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputAudioFixturePlayed( + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionInstructionsAppended() { + val sessionInstructionsAppended = + InstructionsAppendedEvent.builder() + .endMs(1400L) + .eventId("evt_instructions_002") + .startMs(1200L) + .clientEventId("evt_instructions_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionInstructionsAppended(sessionInstructionsAppended) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()) + .contains(sessionInstructionsAppended) + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionInstructionsAppendedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionInstructionsAppended( + InstructionsAppendedEvent.builder() + .endMs(1400L) + .eventId("evt_instructions_002") + .startMs(1200L) + .clientEventId("evt_instructions_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionThinkingAppended() { + val sessionThinkingAppended = + ThinkingAppendedEvent.builder() + .endMs(4800L) + .eventId("evt_thinking_002") + .startMs(4600L) + .clientEventId("evt_thinking_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionThinkingAppended(sessionThinkingAppended) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).contains(sessionThinkingAppended) + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionThinkingAppendedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionThinkingAppended( + ThinkingAppendedEvent.builder() + .endMs(4800L) + .eventId("evt_thinking_002") + .startMs(4600L) + .clientEventId("evt_thinking_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionCommentaryAppended() { + val sessionCommentaryAppended = + CommentaryAppendedEvent.builder() + .endMs(5400L) + .eventId("evt_commentary_002") + .startMs(5200L) + .clientEventId("evt_commentary_001") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionCommentaryAppended(sessionCommentaryAppended) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()) + .contains(sessionCommentaryAppended) + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionCommentaryAppendedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionCommentaryAppended( + CommentaryAppendedEvent.builder() + .endMs(5400L) + .eventId("evt_commentary_002") + .startMs(5200L) + .clientEventId("evt_commentary_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionInputTranscriptDelta() { + val sessionInputTranscriptDelta = + InputTranscriptDeltaEvent.builder() + .delta("A table for two at seven, please.") + .endMs(3400L) + .eventId("evt_input_transcript_001") + .startMs(1600L) + .clientEventId("client_event_id") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputTranscriptDelta(sessionInputTranscriptDelta) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()) + .contains(sessionInputTranscriptDelta) + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionInputTranscriptDeltaRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionInputTranscriptDelta( + InputTranscriptDeltaEvent.builder() + .delta("A table for two at seven, please.") + .endMs(3400L) + .eventId("evt_input_transcript_001") + .startMs(1600L) + .clientEventId("client_event_id") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionOutputTranscriptDelta() { + val sessionOutputTranscriptDelta = + OutputTranscriptDeltaEvent.builder() + .delta("Would you like me to reserve that table?") + .endMs(7200L) + .eventId("evt_output_transcript_001") + .startMs(5400L) + .clientEventId("client_event_id") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputTranscriptDelta(sessionOutputTranscriptDelta) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()) + .contains(sessionOutputTranscriptDelta) + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionOutputTranscriptDeltaRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionOutputTranscriptDelta( + OutputTranscriptDeltaEvent.builder() + .delta("Would you like me to reserve that table?") + .endMs(7200L) + .eventId("evt_output_transcript_001") + .startMs(5400L) + .clientEventId("client_event_id") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionDelegationCreated() { + val sessionDelegationCreated = + DelegationCreatedEvent.builder() + .delegation( + DelegationCreatedEvent.Delegation.builder() + .id("del_abc123") + .target(DelegationCreatedEvent.Delegation.Target.CLIENT) + .responseId("response_id") + .build() + ) + .eventId("evt_delegation_001") + .offsetMs(3600L) + .clientEventId("client_event_id") + .build() + + val sidebandServerEvent = + SidebandServerEvent.ofSessionDelegationCreated(sessionDelegationCreated) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()) + .contains(sessionDelegationCreated) + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionDelegationCreatedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionDelegationCreated( + DelegationCreatedEvent.builder() + .delegation( + DelegationCreatedEvent.Delegation.builder() + .id("del_abc123") + .target(DelegationCreatedEvent.Delegation.Target.CLIENT) + .responseId("response_id") + .build() + ) + .eventId("evt_delegation_001") + .offsetMs(3600L) + .clientEventId("client_event_id") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofResponseEvent() { + val responseEvent = + ResponseEvent.builder() + .event( + ResponseEvent.Event.builder() + .putAdditionalProperty("type", JsonValue.from("response.output_text.delta")) + .putAdditionalProperty("item_id", JsonValue.from("msg_abc123")) + .putAdditionalProperty("output_index", JsonValue.from(0)) + .putAdditionalProperty("content_index", JsonValue.from(0)) + .putAdditionalProperty( + "delta", + JsonValue.from("An outdoor table is available at 7 PM."), + ) + .putAdditionalProperty("sequence_number", JsonValue.from(3)) + .putAdditionalProperty("logprobs", JsonValue.from(listOf())) + .build() + ) + .eventId("evt_response_002") + .clientEventId("client_event_id") + .delegationId("del_responses123") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofResponseEvent(responseEvent) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).contains(responseEvent) + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofResponseEventRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofResponseEvent( + ResponseEvent.builder() + .event( + ResponseEvent.Event.builder() + .putAdditionalProperty( + "type", + JsonValue.from("response.output_text.delta"), + ) + .putAdditionalProperty("item_id", JsonValue.from("msg_abc123")) + .putAdditionalProperty("output_index", JsonValue.from(0)) + .putAdditionalProperty("content_index", JsonValue.from(0)) + .putAdditionalProperty( + "delta", + JsonValue.from("An outdoor table is available at 7 PM."), + ) + .putAdditionalProperty("sequence_number", JsonValue.from(3)) + .putAdditionalProperty("logprobs", JsonValue.from(listOf())) + .build() + ) + .eventId("evt_response_002") + .clientEventId("client_event_id") + .delegationId("del_responses123") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionUsageUpdated() { + val sessionUsageUpdated = + SessionUsageUpdatedEvent.builder() + .eventId("evt_usage_001") + .usage(SessionUsage.builder().seconds(32.5).build()) + .clientEventId("client_event_id") + .contextWindow( + SessionUsageUpdatedEvent.ContextWindow.builder().usageRatio(0.12).build() + ) + .build() + + val sidebandServerEvent = SidebandServerEvent.ofSessionUsageUpdated(sessionUsageUpdated) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).contains(sessionUsageUpdated) + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionUsageUpdatedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionUsageUpdated( + SessionUsageUpdatedEvent.builder() + .eventId("evt_usage_001") + .usage(SessionUsage.builder().seconds(32.5).build()) + .clientEventId("client_event_id") + .contextWindow( + SessionUsageUpdatedEvent.ContextWindow.builder().usageRatio(0.12).build() + ) + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofSessionClosed() { + val sessionClosed = + SessionClosedEvent.builder() + .eventId("evt_closed_001") + .reason(SessionClosedEvent.Reason.CLOSE_REQUESTED) + .session( + SessionResource.builder() + .id("live_abc123") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .delegation(ClientDelegation.builder().build()) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .usage(SessionUsage.builder().seconds(45.8).build()) + .clientEventId("evt_close_001") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofSessionClosed(sessionClosed) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).contains(sessionClosed) + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofSessionClosedRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofSessionClosed( + SessionClosedEvent.builder() + .eventId("evt_closed_001") + .reason(SessionClosedEvent.Reason.CLOSE_REQUESTED) + .session( + SessionResource.builder() + .id("live_abc123") + .expiresAt(1788555600L) + .model(SessionResource.Model.GPT_LIVE_1) + .audio( + SessionResource.Audio.builder() + .audioPcmFormat(AudioFormat.AudioPcm.Rate._24000) + .output( + SessionResource.Audio.Output.builder() + .voice(BuiltInVoice.MARIN) + .build() + ) + .build() + ) + .client( + ClientConfig.builder() + .dataChannel( + DataChannelConfig.builder() + .allowedClientEventsOfStrings(listOf("string")) + .allowedServerEventsAll() + .build() + ) + .build() + ) + .delegation(ClientDelegation.builder().build()) + .input(listOf()) + .instructions( + "Help the caller plan a restaurant reservation. Confirm details before booking." + ) + .store(true) + .build() + ) + .usage(SessionUsage.builder().seconds(45.8).build()) + .clientEventId("evt_close_001") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofErrorEvent() { + val errorEvent = + ErrorEvent.builder() + .error( + Error.builder() + .code("unknown_parameter") + .message("Unknown parameter: 'session.voice'.") + .type("invalid_request_error") + .clientEventId("evt_invalid_001") + .param("session.voice") + .build() + ) + .eventId("evt_error_001") + .clientEventId("client_event_id") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofErrorEvent(errorEvent) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).contains(errorEvent) + assertThat(sidebandServerEvent.infoEvent()).isEmpty + } + + @Test + fun ofErrorEventRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofErrorEvent( + ErrorEvent.builder() + .error( + Error.builder() + .code("unknown_parameter") + .message("Unknown parameter: 'session.voice'.") + .type("invalid_request_error") + .clientEventId("evt_invalid_001") + .param("session.voice") + .build() + ) + .eventId("evt_error_001") + .clientEventId("client_event_id") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + @Test + fun ofInfoEvent() { + val infoEvent = + InfoEvent.builder() + .code("data_channel_permissions") + .eventId("evt_info_001") + .message( + "The frontend data channel is configured with restricted event permissions." + ) + .clientEventId("client_event_id") + .build() + + val sidebandServerEvent = SidebandServerEvent.ofInfoEvent(infoEvent) + + assertThat(sidebandServerEvent.sessionStarted()).isEmpty + assertThat(sidebandServerEvent.sessionUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(sidebandServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(sidebandServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(sidebandServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(sidebandServerEvent.sessionThinkingAppended()).isEmpty + assertThat(sidebandServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(sidebandServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(sidebandServerEvent.sessionDelegationCreated()).isEmpty + assertThat(sidebandServerEvent.responseEvent()).isEmpty + assertThat(sidebandServerEvent.sessionUsageUpdated()).isEmpty + assertThat(sidebandServerEvent.sessionClosed()).isEmpty + assertThat(sidebandServerEvent.errorEvent()).isEmpty + assertThat(sidebandServerEvent.infoEvent()).contains(infoEvent) + } + + @Test + fun ofInfoEventRoundtrip() { + val jsonMapper = jsonMapper() + val sidebandServerEvent = + SidebandServerEvent.ofInfoEvent( + InfoEvent.builder() + .code("data_channel_permissions") + .eventId("evt_info_001") + .message( + "The frontend data channel is configured with restricted event permissions." + ) + .clientEventId("client_event_id") + .build() + ) + + val roundtrippedSidebandServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(sidebandServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSidebandServerEvent).isEqualTo(sidebandServerEvent) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val sidebandServerEvent = + jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { sidebandServerEvent.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/scripts/castiron/custom_code_budget.py b/scripts/castiron/custom_code_budget.py index 096bbdf31..dbfeb7a29 100644 --- a/scripts/castiron/custom_code_budget.py +++ b/scripts/castiron/custom_code_budget.py @@ -57,7 +57,7 @@ def read_policy(repo: Path, revision: str) -> dict[str, Any]: report.git(repo, "show", f"{revision}:{POLICY}"), object_pairs_hook=unique_keys ) if not isinstance(value, dict): - raise ValueError("budget file must be an object") # noqa: TRY004 - malformed policy is a value error + raise ValueError("budget file must be an object") value = cast(dict[str, Any], value) version = value.get("schema_version") if type(version) is not int or version not in (1, 2): @@ -94,6 +94,20 @@ def strict_policy(repo: Path, revision: str) -> bool: ) + +# Audited inventory of the authenticated pre-inventory checker format. These +# historical snapshots bind the checker bytes and manifest through their codegen +# hash. Never infer their inventory from the running compiler or candidate tree. +LEGACY_CHECKPOINT_INVENTORIES = { + "e504abba2701f4733eb4b8ce26856cca596e13feab1e7e323adc8e7d2ccb64bc": frozenset( + { + b".github/workflows/castiron-custom-code-comment.yml", + b".github/workflows/castiron-custom-code.yml", + } + ), +} + + def checkpoint_github_inventory(source: bytes) -> frozenset[bytes]: """Read literal compiler inventory from verified checkpoint data, without execution.""" values = [ @@ -105,6 +119,10 @@ def checkpoint_github_inventory(source: bytes) -> frozenset[bytes]: for target in node.targets ) ] + if not values: + historical = LEGACY_CHECKPOINT_INVENTORIES.get(hashlib.sha256(source).hexdigest()) + if historical is not None: + return historical if len(values) != 1: raise ValueError("checkpoint must contain one generated GitHub inventory") value = values[0] diff --git a/scripts/castiron/generated-files.json b/scripts/castiron/generated-files.json index 8b6dbaaf7..b5ec9c602 100644 --- a/scripts/castiron/generated-files.json +++ b/scripts/castiron/generated-files.json @@ -179,7 +179,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateParams.kt": { "mode": "100644", - "sha256": "ead59970710cab8751756a20622c38efeba4d545588ba96992680073caae33bb" + "sha256": "a7f46cb28b2f1337f56c6efd1f146c3d0e88beb5229c5ee1efdcff34a42ecd88" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateActivateResponse.kt": { "mode": "100644", @@ -203,7 +203,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateParams.kt": { "mode": "100644", - "sha256": "664dffe390aa2efd942f3b6dcb50efbf3a8dadedbfde6823b4a667205fee7ff0" + "sha256": "c30a6000a4c9b2822e9880be1e72e10ff49576f5392ae372fe4ce2194d4874c9" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/certificates/CertificateDeactivateResponse.kt": { "mode": "100644", @@ -451,7 +451,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteCreateParams.kt": { "mode": "100644", - "sha256": "843442ed6c8c9b9b58514b17d8da69f7533f1ac4d7fffd0af0b5ba0fd6fe5c00" + "sha256": "83c054ddea649e5b6325a886788f807fdcfcac74e9c43b2292418c397252f143" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/invites/InviteDeleteParams.kt": { "mode": "100644", @@ -571,7 +571,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateParams.kt": { "mode": "100644", - "sha256": "452965935011bda7791ad1b9814d603c11deb2acc09fc61d374a1a305caf2b46" + "sha256": "c2c72842cd95b6ec0369e1e33c43c9bb2755df08353624f49a8f550f4ef3f3b0" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateActivateResponse.kt": { "mode": "100644", @@ -591,7 +591,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateParams.kt": { "mode": "100644", - "sha256": "64d921e1c5fc9160700fee899a79bdd89740498dbde6156725e18a089a82f836" + "sha256": "6000f452a31dff9cf46d8dfc4f9e392d17ddcb794e26a4d37258c29802b45de3" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/certificates/CertificateDeactivateResponse.kt": { "mode": "100644", @@ -731,7 +731,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ModelPermissionUpdateParams.kt": { "mode": "100644", - "sha256": "22bab2c3a39ca4b589a3fbdffa404cf5bbeb1046396c16eb5bd8c14a866c30b9" + "sha256": "f77657456e80ea0f87522ec52826005f5c1406a5d876a7ebf4b9d2ee2d34785d" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/modelpermissions/ProjectModelPermissions.kt": { "mode": "100644", @@ -767,7 +767,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleCreateParams.kt": { "mode": "100644", - "sha256": "7bf3fd83f296beb161b63e0549e5cfba34a5a5b379db7847d3c26af0cd46d368" + "sha256": "56061529b38b58366bfd3bc0efc8f4e80fe6d9dc3e5fbd5a48563d35faf2621f" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleDeleteParams.kt": { "mode": "100644", @@ -799,7 +799,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/roles/RoleUpdateParams.kt": { "mode": "100644", - "sha256": "b8601dc4ef19ec8f440388fefdd0e8cff7b2696c8ed2f474a75f885c5490d049" + "sha256": "f3f66fcf8b99e57d01374d6e381419020f626ad6b03e197b57736ada808b2b0c" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/ProjectServiceAccount.kt": { "mode": "100644", @@ -847,7 +847,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateParams.kt": { "mode": "100644", - "sha256": "9e180ba050a01aea9b51d33fa66716ae170b7184300bc73c62950b3b024c8d42" + "sha256": "31992694f1c31c7238ad992efd3076715b5347ca5e0364078c7c6bd0551a6e62" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/projects/serviceaccounts/apikeys/ApiKeyCreateResponse.kt": { "mode": "100644", @@ -1003,7 +1003,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleCreateParams.kt": { "mode": "100644", - "sha256": "9ff65738538013a1dd23ec2a343b1019b619a28457159d7e2e26b9350a0572fb" + "sha256": "bf47a9fed482c659be69cd74c57e488a657d903f1e32aaefbed306745fbb914f" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleDeleteParams.kt": { "mode": "100644", @@ -1035,7 +1035,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/roles/RoleUpdateParams.kt": { "mode": "100644", - "sha256": "b29297110ee2704001e2c2a7503255865ebd67f837e5aea466da3acc1dd7fb7a" + "sha256": "f3cab318578380d52e09d498230450502ebfae42d65b73819e4e6f06b18afe19" }, "openai-java-core/src/main/kotlin/com/openai/models/admin/organization/spendalerts/OrganizationSpendAlert.kt": { "mode": "100644", @@ -1447,7 +1447,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateParams.kt": { "mode": "100644", - "sha256": "e969f72182c737bd2d7d3f83fa6fee0f5c24763a672844fa68548f073fcf3fd4" + "sha256": "a20159a56a3369d1b377244dce69fc5eb0efb100652baa309626ce26d9fd0a8c" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentCreateSubagentCallItem.kt": { "mode": "100644", @@ -1723,7 +1723,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentUpdateParams.kt": { "mode": "100644", - "sha256": "295fc1a8959fcce09ae5de7f623ec904afb07b7a07c159d987915e446072a5e0" + "sha256": "a830f5c2b736ad3e15e90d4c1806ff58ea6cdd1444c41fe23b99e4eb6ed2b80a" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/AgentWaitForSubagentsCallItem.kt": { "mode": "100644", @@ -1859,7 +1859,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentCreateParams.kt": { "mode": "100644", - "sha256": "7c08519853862959f4f627d8dad493b9246bd6f703118429a391be5c81c959b0" + "sha256": "d1fb76d8dd3167af734304d8d19e8128e8c906e49d9706b8e39b9235c8aa1c73" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/EnvironmentInfo.kt": { "mode": "100644", @@ -1919,7 +1919,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateCreateParams.kt": { "mode": "100644", - "sha256": "5e2730f542ac35ffe2052d27697f39ee379494c3474c668301a107e57dae962a" + "sha256": "1424db3b9b075ea3c5c7040f1b1469514b2f88e1f38f446ef97274182cb22180" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateDeleteParams.kt": { "mode": "100644", @@ -1947,11 +1947,11 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/environments/templates/TemplateUpdateParams.kt": { "mode": "100644", - "sha256": "585b4f600e758eacbfe353ca0c164c63f7ea70f91d6ae1a635c390f33dcff0bf" + "sha256": "e58c9e56fbae150781b5f12f9cc0e72e78f8b93459fc60337fd23f26daeef306" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionCreateParams.kt": { "mode": "100644", - "sha256": "814fa2f5692a56190c33dfd5d07b66152e29e871e9a664d58c0846340a3bbca7" + "sha256": "c628991662771833eb529aa7d082d3827bbeabf7069c72783f0002513ac6de98" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/SessionDeleteParams.kt": { "mode": "100644", @@ -2019,7 +2019,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventCreateParams.kt": { "mode": "100644", - "sha256": "f6085b44c1a2401787a45cd42fe3a32ea66ae786566260f6945aabc8972b963d" + "sha256": "25bb2238075c08a8214b194a1abd9cd1d60eddbae7138a79cb8e6735e21641f6" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/agents/sessions/events/EventStreamParams.kt": { "mode": "100644", @@ -2299,7 +2299,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantCreateParams.kt": { "mode": "100644", - "sha256": "2243b1e17e17ad1ff0bbc39a690cdf2d64087038b4616404527569d6b7eec52f" + "sha256": "2ae90d91c28dbb982d41024893761c2837e035d944eb9387bc853469c39074aa" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantDeleteParams.kt": { "mode": "100644", @@ -2339,7 +2339,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/AssistantUpdateParams.kt": { "mode": "100644", - "sha256": "664cc0c90959c0c98b1796aa5d587cfb472c8501fa1f256db3837fbcc42d68b0" + "sha256": "4834738e1f3702da222b185a17bc513c930fd02e863da84de98f8e39a9664fa3" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/assistants/CodeInterpreterTool.kt": { "mode": "100644", @@ -3163,7 +3163,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseCreateParams.kt": { "mode": "100644", - "sha256": "5424df466d4a84190c93e4d4108c7f6b0e853148db00a6e7c7f5b3fb36381a62" + "sha256": "8f89b4710b9fa4a40d7cf554dbd5854f96f91048ac2012d25f48f3a13426b52e" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/responses/ResponseDeleteParams.kt": { "mode": "100644", @@ -3191,7 +3191,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountParams.kt": { "mode": "100644", - "sha256": "132471a09d5a37e044e600c028d1c7d2eb63f8efa7dfef44ada5fcf719b21050" + "sha256": "6b2f626e3fc1d5ae25210395875d9459be1d11d44d25e6822c3b9e364ce38f16" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/responses/inputtokens/InputTokenCountResponse.kt": { "mode": "100644", @@ -3219,11 +3219,11 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateAndRunParams.kt": { "mode": "100644", - "sha256": "6f39a41165609f4e75fe340fbd327023c5d24f00223c09661e0abaea6eb314ab" + "sha256": "62f1bc560390b71daf9ad4d8f3f76cc4a48baf0e15d51456367cc2a02ccd8cf5" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadCreateParams.kt": { "mode": "100644", - "sha256": "6a70cbe5bb5205440e93a69b1f1c7a8afddf0dc7e23d4951269b63b9db895122" + "sha256": "a294eb54e22f99a1ec1e4b632915be548a624dae71534d919c141b048db7d6b9" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/ThreadDeleteParams.kt": { "mode": "100644", @@ -3315,7 +3315,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageCreateParams.kt": { "mode": "100644", - "sha256": "3731396eac6f6a1b42f7be78c397fbd3ae1b302c545303cf0f76536a4bab27e0" + "sha256": "9a02b2acc53de2195ca9e80f5834e0f3aeecde1de1323428499d0158e2552983" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/messages/MessageDeleteParams.kt": { "mode": "100644", @@ -3399,7 +3399,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunCreateParams.kt": { "mode": "100644", - "sha256": "51cd237663533e986a4f0f7484152a39de1bba59607e25fb860ac26efbb501c1" + "sha256": "6fe116242b54e7396602740ff6a5b0af145d5104b5e3af8fe69ad96e1c7901e1" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunListPage.kt": { "mode": "100644", @@ -3427,7 +3427,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunSubmitToolOutputsParams.kt": { "mode": "100644", - "sha256": "d9b565df506cb34f57655e7fbf4741d797331928a98ec0d7f5068d5a4fd762d2" + "sha256": "165c7eae1bed567667074fc449de651446f0c0437418b14e7900a736b9950f75" }, "openai-java-core/src/main/kotlin/com/openai/models/beta/threads/runs/RunUpdateParams.kt": { "mode": "100644", @@ -3575,7 +3575,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt": { "mode": "100644", - "sha256": "909b1c850161f30e6241ecf47048d9986de4785cda966ac69f9dd601b0c0a787" + "sha256": "a46d65eba1a65281e19cc176773cfc3b5428f6423db9024c9172edbac5f49a16" }, "openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCustomTool.kt": { "mode": "100644", @@ -3735,7 +3735,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateParams.kt": { "mode": "100644", - "sha256": "80e6bf3fc6ef8a34f7a7e52c72f0348f831759e7b6cac0d4c14fea05e31587a5" + "sha256": "1859e0f21c0dfee9413e135069c7e4d9f0dd531bc9d8b8ccb6e672064b386a4c" }, "openai-java-core/src/main/kotlin/com/openai/models/containers/ContainerCreateResponse.kt": { "mode": "100644", @@ -3835,7 +3835,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationCreateParams.kt": { "mode": "100644", - "sha256": "93e424a81ffc9186dfbb711ec439287f52fa6bac2cb671101f8943b3277d2860" + "sha256": "676a8fcde6a83822f6ce26c7b696cee9291d2f4998976c9e52385a18ae032427" }, "openai-java-core/src/main/kotlin/com/openai/models/conversations/ConversationDeleteParams.kt": { "mode": "100644", @@ -3879,7 +3879,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemCreateParams.kt": { "mode": "100644", - "sha256": "69a5f786762d5d85d51336c3fc8047b825a5d8141502d3598052878aca4a0e20" + "sha256": "07a73d1fef505015e625314706684ba271b21b21eb3bf4831072226acb06911f" }, "openai-java-core/src/main/kotlin/com/openai/models/conversations/items/ItemDeleteParams.kt": { "mode": "100644", @@ -3915,7 +3915,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionCreateParams.kt": { "mode": "100644", - "sha256": "bd189d33996fd8bfd230b4d0b81d65ba4c3c21676473b8ad865b75a297ad2fd9" + "sha256": "3e87835651f9757118634ee047cf144d917c422e3f41a0fc0aebeac053fb002f" }, "openai-java-core/src/main/kotlin/com/openai/models/decisions/DecisionInputImage.kt": { "mode": "100644", @@ -3951,7 +3951,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateParams.kt": { "mode": "100644", - "sha256": "3416da3e6a43311f9947887efd5b4a6feb84278d3ce07fac8e9bb70dee7268e8" + "sha256": "a39e672522927ef8aa86140f2923626d7b4e4e65238f8a2660bd06dbc0fcb711" }, "openai-java-core/src/main/kotlin/com/openai/models/evals/EvalCreateResponse.kt": { "mode": "100644", @@ -4175,7 +4175,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateParams.kt": { "mode": "100644", - "sha256": "2f8b126ec7019bbd593746cf0862f3a4f7066304d4bdb32e134f8f750dc2f503" + "sha256": "f2ddc8c309a6891e55844dac3ec5b2231515051d325dce71d6b0d249e29f61e5" }, "openai-java-core/src/main/kotlin/com/openai/models/finetuning/checkpoints/permissions/PermissionCreateResponse.kt": { "mode": "100644", @@ -4239,7 +4239,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobCreateParams.kt": { "mode": "100644", - "sha256": "ebeb676a1fb779e7ed722a0955f9dcfc4b9ec9e4ad50e46100fd160c91f69e0d" + "sha256": "2054999311d9144cb0f9ff3977a2dd81e03016da9ddb37e27e039aa949606e8d" }, "openai-java-core/src/main/kotlin/com/openai/models/finetuning/jobs/JobListEventsPage.kt": { "mode": "100644", @@ -4513,6 +4513,10 @@ "mode": "100644", "sha256": "1f07d8c49da6bdf638b0cae95d565c9f93a4c967c274da9a75396bc554731b16" }, + "openai-java-core/src/main/kotlin/com/openai/models/live/LiveWebSocketBinding.kt": { + "mode": "100644", + "sha256": "4777a47fa314917ddccacc22fb1fed745b46d8e04480d2edc66b7f5ae85deac8" + }, "openai-java-core/src/main/kotlin/com/openai/models/live/MediaSessionConfig.kt": { "mode": "100644", "sha256": "234ead213b942c60fb7ff1f3c986c47359024894e7ebd9edbeb793527e0f9344" @@ -4629,6 +4633,10 @@ "mode": "100644", "sha256": "b366757224662cb29dad65840d59a37477f329430213babb6b473b349e0b9342" }, + "openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkWebSocketBinding.kt": { + "mode": "100644", + "sha256": "ab714e06769a182c4ee7004d8e75d68990050dd6e9e00b95dccc8e986c704fbe" + }, "openai-java-core/src/main/kotlin/com/openai/models/live/sessions/SessionAcceptParams.kt": { "mode": "100644", "sha256": "eb232268e4e9dbdecb442f96eacf2f941b5f821645f02f745096fc85234acbba" @@ -4657,6 +4665,18 @@ "mode": "100644", "sha256": "0f4d431e91029111aa66bb5b922af209f3a0b46da90b518a22efa79fad48df5e" }, + "openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandClientEvent.kt": { + "mode": "100644", + "sha256": "1357f07b6cac6797b19173c310769c97881a85bf287f2e9d59d6cac638e0d536" + }, + "openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandServerEvent.kt": { + "mode": "100644", + "sha256": "cc05c3d46719c797fcdc044386267b696b3d18fe7b6555cf6f0ef9d56abed48c" + }, + "openai-java-core/src/main/kotlin/com/openai/models/live/sideband/SidebandWebSocketBinding.kt": { + "mode": "100644", + "sha256": "86aa62e21a985715c1f8bed067a7abb2d6d485413da6fb1a165a43596dd86601" + }, "openai-java-core/src/main/kotlin/com/openai/models/models/Model.kt": { "mode": "100644", "sha256": "b3d50f5dd9dc06a1bbb94eae6394353414b980eb630f0ff54a5a486a6566d619" @@ -5043,7 +5063,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationServerEvent.kt": { "mode": "100644", - "sha256": "5266e21f0ef3f23fd4bbd22db38f7e10e28c4adcc43fb41ae92beaa5de91fac5" + "sha256": "c3e5939eb5c40605b1574294e693d69fda224eb7142d0ea4003f20c601877e12" }, "openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeTranslationSession.kt": { "mode": "100644", @@ -5085,6 +5105,10 @@ "mode": "100644", "sha256": "bfa04bf9aac067ad6264547fca3368419a26b59957043089362c546366e11c90" }, + "openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeWebSocketBinding.kt": { + "mode": "100644", + "sha256": "f216de06a7096e4a7231b9ce44f5a268baa72832343f9b1adfa8c2a6dff76580" + }, "openai-java-core/src/main/kotlin/com/openai/models/realtime/ResponseAudioDeltaEvent.kt": { "mode": "100644", "sha256": "7ce48d7968b1ff28d0cb2272d2f823915f832ea7f6db0057d1dc9dfebd42c6ac" @@ -5229,6 +5253,10 @@ "mode": "100644", "sha256": "636aec7433a38c63d9d16275a6bf35973bd8e85a54aad6bd06b792f85c8b378b" }, + "openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/TranslationWebSocketBinding.kt": { + "mode": "100644", + "sha256": "38e0921833e565d0986f6aa9593aa72f9457772df6ee63e208d5721c54161f65" + }, "openai-java-core/src/main/kotlin/com/openai/models/realtime/translations/clientsecrets/ClientSecretCreateParams.kt": { "mode": "100644", "sha256": "da9618bd5379f828388047b1c55b02ed0e15f1f1294c7cd9507ed08e485d1413" @@ -5435,7 +5463,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt": { "mode": "100644", - "sha256": "3ef531dd18c7295b12614fe925140b901bbfcd855d6de598a41201800d6af27d" + "sha256": "8d5c0bd7b1713e91d945989c8b5a98ad4fcc0685e9a725b622fdea123b65e57a" }, "openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreatedEvent.kt": { "mode": "100644", @@ -5837,6 +5865,10 @@ "mode": "100644", "sha256": "9ca60dcb3efc89bd59b44f201a7f1dc7b4dc92af0714abb68763b579cbb16eaf" }, + "openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseWebSocketBinding.kt": { + "mode": "100644", + "sha256": "fd35dae2c4747b206fa11fd0d0958406ce9a91b04ed04705da00e3ac4e67bdee" + }, "openai-java-core/src/main/kotlin/com/openai/models/responses/ResponsesClientEvent.kt": { "mode": "100644", "sha256": "9d99d2f9b45955eba140ab61a63d2c6f027af6d70dfc2b3afb1871f4ee5ef103" @@ -5919,7 +5951,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountParams.kt": { "mode": "100644", - "sha256": "ca45e3b301bf9b975a29775149d8806fc6b1ca5ce5220545363989cd14c4d743" + "sha256": "9646a828404ced609421b6d0329d02d8c278183e62143f3cf19de5c02a58cb32" }, "openai-java-core/src/main/kotlin/com/openai/models/responses/inputtokens/InputTokenCountResponse.kt": { "mode": "100644", @@ -6035,7 +6067,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCompleteParams.kt": { "mode": "100644", - "sha256": "fb383b9db6c225ef6dc220599e4b8ac80e03850e9838754c039787559ddc9ef9" + "sha256": "7d4356447627204aa39596378ae68c2605b18957d1401df050ba6bae28af404c" }, "openai-java-core/src/main/kotlin/com/openai/models/uploads/UploadCreateParams.kt": { "mode": "100644", @@ -6083,7 +6115,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreCreateParams.kt": { "mode": "100644", - "sha256": "372d2f2cc48c7762f371124b32f7e0e35663834ef2992805ae962933ff1977a7" + "sha256": "8065638bc1ce3e84c29794285c60bd07f87cbc23637f93ee239eeb1229039fc1" }, "openai-java-core/src/main/kotlin/com/openai/models/vectorstores/VectorStoreDeleteParams.kt": { "mode": "100644", @@ -6143,7 +6175,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchCreateParams.kt": { "mode": "100644", - "sha256": "21db14756470ce97815e2d3b06d5e260de42961ca8b26e99b3483e5e0940b96c" + "sha256": "4780b6ca344f61ef093778b5fab2be46285b7d183ed8b6953f701ec9118c4b9c" }, "openai-java-core/src/main/kotlin/com/openai/models/vectorstores/filebatches/FileBatchListFilesPage.kt": { "mode": "100644", @@ -6447,7 +6479,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookCreateParams.kt": { "mode": "100644", - "sha256": "e5d0f7577d031960a7491f71adcd33a129a94dfb11829acc800720c1fd89b4f1" + "sha256": "3cd9f34c4805afd156f4fcdc511df1e28c01d67ffa9ddf5bcf97db63e2fc1d5b" }, "openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookDeleteParams.kt": { "mode": "100644", @@ -6499,7 +6531,7 @@ }, "openai-java-core/src/main/kotlin/com/openai/models/webhooks/WebhookUpdateParams.kt": { "mode": "100644", - "sha256": "777840eb19d8953c60c9e57617013a369058adc9af9af4a270ec4011353a3b71" + "sha256": "ff626b875ca73d708d71cb1fa68f3c03cfa202f8e6b1ccf84d08dad5fd55158f" }, "openai-java-core/src/main/kotlin/com/openai/models/webhooks/eventtypes/EventTypeListParams.kt": { "mode": "100644", @@ -12259,7 +12291,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt": { "mode": "100644", - "sha256": "298dce8d34c426b32d8487b0460426b52b5d039ff70efe9e6598ab1b863ff9c6" + "sha256": "212a68363e7fd75e1176edd87a8aa15b1fc72a4af832986419ffc442ec8e47f8" }, "openai-java-core/src/test/kotlin/com/openai/models/live/CommentaryAppendEventTest.kt": { "mode": "100644", @@ -12403,7 +12435,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt": { "mode": "100644", - "sha256": "89c91523ca6b99b8d21c296975c4abc79cdcf554ab593df01cce88d1ed14b9f2" + "sha256": "8ca9a5f1cd4d6deaee62d818ebe61173be0f015432fb9c03fe32c1259f1556e3" }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionCloseEventTest.kt": { "mode": "100644", @@ -12411,7 +12443,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionClosedEventTest.kt": { "mode": "100644", - "sha256": "4756778383b86578d5f464a27c74d94941ea711cbd7720bc32d03e8e4375198e" + "sha256": "2e82e02a621567aca1044b6e545c826ce51f25eb5f8157428cedbfe4807e5c93" }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionConfigTest.kt": { "mode": "100644", @@ -12423,11 +12455,11 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionStartEventTest.kt": { "mode": "100644", - "sha256": "323213a1520d5c2184779e0547b4f9b875662673c5212065bd7e41a23c380b11" + "sha256": "e760b5ebc257ad97f1d96bc2fb0ad88ff1d6755f3dafdb518069df128993c68a" }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionStartedEventTest.kt": { "mode": "100644", - "sha256": "094cc0d8f6cc7dba90629f7867e50bb1d1ba58d06a6d2093628d2fd3322fe006" + "sha256": "2b0e82b202168032abc69467e5e6c480337ac5942f945d83c6238c149b7286fc" }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionUpdateConfigTest.kt": { "mode": "100644", @@ -12439,7 +12471,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionUpdatedEventTest.kt": { "mode": "100644", - "sha256": "f30dc967929781a986e3cb8ebdea39598fad2ca0a3734d17c98e20cc41220f12" + "sha256": "0fd8127d5cf83e85f9c5398a360cafe70b03c68cc6fe9a840c3e43f136f5781e" }, "openai-java-core/src/test/kotlin/com/openai/models/live/SessionUsageTest.kt": { "mode": "100644", @@ -12463,7 +12495,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkServerEventTest.kt": { "mode": "100644", - "sha256": "df2e9d8513d4092a40378440221a3065c67e321f473535eda4a1ff757adf4839" + "sha256": "4616ad121dbb9b055e7b8fdd94137de3f19c930e62043ccf926c3a0480bb188a" }, "openai-java-core/src/test/kotlin/com/openai/models/live/sessions/SessionAcceptParamsTest.kt": { "mode": "100644", @@ -12493,6 +12525,14 @@ "mode": "100644", "sha256": "576618ef89abd533849424afdf965a93b942bc6df5b6b1762ff03f772f5912d0" }, + "openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandClientEventTest.kt": { + "mode": "100644", + "sha256": "36fab748a9f71879a9ffecd7327c54952230c266f2d8f98207ea959f3d0487d1" + }, + "openai-java-core/src/test/kotlin/com/openai/models/live/sideband/SidebandServerEventTest.kt": { + "mode": "100644", + "sha256": "6e1488e45273f5da27e10fdf1174504efc28ac79e84a4f84d8e18805383c8a96" + }, "openai-java-core/src/test/kotlin/com/openai/models/models/ModelDeleteParamsTest.kt": { "mode": "100644", "sha256": "d1fa52a95f1f121d062315c98608e437390412d84dc0008cbe491541d27875cc" @@ -14219,7 +14259,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/CompletionServiceAsyncTest.kt": { "mode": "100644", - "sha256": "fcb1eaf3f607607187329842ba08e662bf32914d6960c321d8f4be4e5d1b6907" + "sha256": "9e536f5fa5c8d87063ec3dc22d92295afc515f4da013835c3ec6f21830cae684" }, "openai-java-core/src/test/kotlin/com/openai/services/async/ContainerServiceAsyncTest.kt": { "mode": "100644", @@ -14251,7 +14291,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/ImageServiceAsyncTest.kt": { "mode": "100644", - "sha256": "e8bb6db2060ad90dfd33bb329307b640435497080063e6bd0d9ff69a02b366eb" + "sha256": "482bafd3592b01f9336da1ae08f0344ad1d8b6e4c7f78b144060a29cf6bfab3a" }, "openai-java-core/src/test/kotlin/com/openai/services/async/LiveServiceAsyncTest.kt": { "mode": "100644", @@ -14267,7 +14307,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/ResponseServiceAsyncTest.kt": { "mode": "100644", - "sha256": "c79f677a06dd060f30a01ab6328d4a7b496ea2bf930c51991ded60b0035f35e4" + "sha256": "dc6848b452decac13ca94a6ee3c839643b88f299fda4b0d283c9d3b0ba6d09e3" }, "openai-java-core/src/test/kotlin/com/openai/services/async/SkillServiceAsyncTest.kt": { "mode": "100644", @@ -14419,7 +14459,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/audio/TranscriptionServiceAsyncTest.kt": { "mode": "100644", - "sha256": "44fb4c7abea303d05aeb92adeac3df650a758f1d3e230ed3d4f9d61ceb259eab" + "sha256": "809b3db42e91f92028173738734e58e58486f307f2477a00bd299a36cff28afd" }, "openai-java-core/src/test/kotlin/com/openai/services/async/audio/TranslationServiceAsyncTest.kt": { "mode": "100644", @@ -14435,11 +14475,11 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/ResponseServiceAsyncTest.kt": { "mode": "100644", - "sha256": "24579f1d28cee2f323931f4b2cebda54cbf8eaffe211df4e867e6d8c581ed5e3" + "sha256": "5724ffe7bca2f37752bcb252b17f0fe9f33bddfcf8c5e6dfd21c37c0daa41ba1" }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/ThreadServiceAsyncTest.kt": { "mode": "100644", - "sha256": "221559cc3acf1aeef2c3ebad8287ceb872affde8623860617f835d809ba46ed7" + "sha256": "c52409f4124be04f0fd2910214821c6e273d320ef4293d64f88f837031aeb094" }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/agents/EnvironmentServiceAsyncTest.kt": { "mode": "100644", @@ -14447,7 +14487,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/agents/SessionServiceAsyncTest.kt": { "mode": "100644", - "sha256": "c139b1dd6494da07d68718d717919077085cffc0540cc97fc1f00948465b58bc" + "sha256": "a87d33300c14161f7d5951dfd6e2b369a18309dcf924ae50e7a0f9785df69ef5" }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/agents/VaultServiceAsyncTest.kt": { "mode": "100644", @@ -14467,7 +14507,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/agents/sessions/EventServiceAsyncTest.kt": { "mode": "100644", - "sha256": "7b22cc900f411304bfd3b7776552b1613c2d7e9a91f24f11b6292fe0f3687fc4" + "sha256": "ad22a21302aef559e30bb2339d21c21170946bae41b493919a89864b5bdd8abb" }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/agents/sessions/ItemServiceAsyncTest.kt": { "mode": "100644", @@ -14527,7 +14567,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/threads/RunServiceAsyncTest.kt": { "mode": "100644", - "sha256": "d1d7434fc1e4010fc47506ff807f4d769b77306c72c1d87de784ec2c708def75" + "sha256": "d89e35a941d363fc044997cf5381e14d573035bc6bdffdf801a0ef150ab64727" }, "openai-java-core/src/test/kotlin/com/openai/services/async/beta/threads/runs/StepServiceAsyncTest.kt": { "mode": "100644", @@ -14535,7 +14575,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/async/chat/ChatCompletionServiceAsyncTest.kt": { "mode": "100644", - "sha256": "39cadb284b17dd406374d9d97afa216e619efa3f49635daac91e4bdf7cdd9b88" + "sha256": "d5a2c93b0cbbd8072d79c34b3ae6a43328744fcdf887216a2e6f3a87b8c1adb9" }, "openai-java-core/src/test/kotlin/com/openai/services/async/chat/completions/MessageServiceAsyncTest.kt": { "mode": "100644", @@ -14643,7 +14683,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/CompletionServiceTest.kt": { "mode": "100644", - "sha256": "47ecff6e31cab4120ceef7f0c6d4ea6b9f844341751bf368dc2800f6787f527a" + "sha256": "e70b544a9dca4ae2941f614b8625a9daaae1d00954da23336f21c0caaaa14a6c" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/ContainerServiceTest.kt": { "mode": "100644", @@ -14675,7 +14715,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/ImageServiceTest.kt": { "mode": "100644", - "sha256": "b02f641c7a7befc0290bd71723ebb036bf23523d5cd34e37a1a8657c52f70faa" + "sha256": "88c8be9ee87820c464b35fd11f49f9b81a816c45419e2456716cf86648c81c40" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/LiveServiceTest.kt": { "mode": "100644", @@ -14691,7 +14731,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/ResponseServiceTest.kt": { "mode": "100644", - "sha256": "030baedf3004bd738fff822bcb2b917cf7254864a15f8c0177b084fecdf498bc" + "sha256": "426943242b9700ad5120d73d1a2e69535cf8abf144402a543cc649d681f72c25" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/SkillServiceTest.kt": { "mode": "100644", @@ -14843,7 +14883,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/audio/TranscriptionServiceTest.kt": { "mode": "100644", - "sha256": "f5a1142f643d3ad31eff8ee1264620acdb2c97d24107767314e480d483a18aa9" + "sha256": "928f623e42c4e9cfeb53fc1d58ec1efda4b54365ba9c70dcdc3fc46a290c6dd9" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/audio/TranslationServiceTest.kt": { "mode": "100644", @@ -14859,11 +14899,11 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/ResponseServiceTest.kt": { "mode": "100644", - "sha256": "54b643b5deff24b72fa4e1f2e17f096f56e39b8d91bffea80a1083c132b68900" + "sha256": "d885aaa6f8c03b299d254782ff5cb6b9747d4eb1b86dabb58ecb56187dbd8466" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/ThreadServiceTest.kt": { "mode": "100644", - "sha256": "2b9dbd0e3124621403b1d7098a06540aa29523871955f631780efec8b5a14eed" + "sha256": "d3c92011757ee7fcc393f3cbce67fa42f91b2d5656b91818bd4b65cdd0e2a1cd" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/agents/EnvironmentServiceTest.kt": { "mode": "100644", @@ -14871,7 +14911,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/agents/SessionServiceTest.kt": { "mode": "100644", - "sha256": "53f631715b812eb2a15e23d7074b306456bf6156704f7a049b0b90fe4f3556f2" + "sha256": "7fc6fe5535b921cbb2f02f379b94c55179fe8b8bbcf572d0a41ddea7622816d2" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/agents/VaultServiceTest.kt": { "mode": "100644", @@ -14891,7 +14931,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/agents/sessions/EventServiceTest.kt": { "mode": "100644", - "sha256": "a0c6bd49dd9f6f82a8acd70d908df3106eeb96c11c7db8c463d7a78ee89829a3" + "sha256": "81b0c8097868aa06864d15df68d2736a4c9084f0555abe74f0ef852622b1ca0f" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/agents/sessions/ItemServiceTest.kt": { "mode": "100644", @@ -14951,7 +14991,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/threads/RunServiceTest.kt": { "mode": "100644", - "sha256": "2b9d3d35262980cca6bcff8bebf455a6902de4bd0a96b2ac678ac51d1a605dd2" + "sha256": "3be791bff1d53ec6931a2e8b6c9725d4b7a8f96d04f356b5d7bd1dfbfd879aea" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/beta/threads/runs/StepServiceTest.kt": { "mode": "100644", @@ -14959,7 +14999,7 @@ }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/chat/ChatCompletionServiceTest.kt": { "mode": "100644", - "sha256": "106c2284574b02a39da5857ee24265ae604d8dcbd6855826ca3b6e7eff4f734e" + "sha256": "07b556f63faaa2ccae658b37ead33f3e3acf732cc1e7c54be95dc34a14042346" }, "openai-java-core/src/test/kotlin/com/openai/services/blocking/chat/completions/MessageServiceTest.kt": { "mode": "100644", @@ -15071,7 +15111,7 @@ }, "scripts/castiron/custom_code_budget.py": { "mode": "100644", - "sha256": "deeb0d41058fbc311d53c2bbaba11de5737cab3a91a643023a256f61fcf7cd4c" + "sha256": "e6f19706c26f9b996f7d6d5f8a654e54fbc8c49a5d2fce045c1996b119feee33" }, "scripts/castiron/custom_code_report.py": { "mode": "100644",