From 8dae95545d93d5d77cee16d81235fbb10bc1cae0 Mon Sep 17 00:00:00 2001 From: yoni-oai <338284798+yoni-oai@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:03:40 +0000 Subject: [PATCH] feat(api): support audio fixture playback in Live sessions Castiron-Internal-PR: https://github.com/openai/openai-java-internal/pull/269 Castiron-Source-SHA: 5efa70326247e2eaedff12cafbcc5067257e6f2a Castiron-Public-Base-SHA: 75b46c0b774cadd323b9b31d24ce150a56700d91 --- .castiron.stats.yml | 14 +- api_reference/openapi.transformed.yml | 107 ++++++- .../client/okhttp/LargePayloadContractTest.kt | 42 +++ .../okhttp/LiveSidebandConnectionTest.kt | 58 ++++ .../openai/helpers/LiveSidebandConnection.kt | 6 +- .../com/openai/models/live/ClientEvent.kt | 113 ++++++- .../live/OutputAudioFixturePlayEvent.kt | 275 ++++++++++++++++++ .../live/OutputAudioFixturePlayedEvent.kt | 271 +++++++++++++++++ .../live/OutputAudioFixturePlayingEvent.kt | 273 +++++++++++++++++ .../com/openai/models/live/ServerEvent.kt | 190 ++++++++++-- .../models/live/forks/ForkClientEvent.kt | 114 +++++++- .../models/live/forks/ForkServerEvent.kt | 130 +++++++++ .../com/openai/models/live/ClientEventTest.kt | 53 ++++ .../live/LiveVisitorCompatibilityTest.kt | 86 ++++++ .../live/OutputAudioFixturePlayEventTest.kt | 35 +++ .../live/OutputAudioFixturePlayedEventTest.kt | 42 +++ .../OutputAudioFixturePlayingEventTest.kt | 42 +++ .../com/openai/models/live/ServerEventTest.kt | 160 ++++++++++ .../models/live/forks/ForkClientEventTest.kt | 55 ++++ .../models/live/forks/ForkServerEventTest.kt | 162 +++++++++++ 20 files changed, 2175 insertions(+), 53 deletions(-) create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayEvent.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayedEvent.kt create mode 100644 openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayingEvent.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/LiveVisitorCompatibilityTest.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayEventTest.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayedEventTest.kt create mode 100644 openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayingEventTest.kt diff --git a/.castiron.stats.yml b/.castiron.stats.yml index fca493c07..ef2ccaf1c 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: 4b4aafa4-81c7-4a2b-a856-845ab3ce3fd3 -openapi_spec_hash: e004507264375728a48a36cfa571544e -openapi_transformed_spec_hash: bb7d73817be9bf23b859a5dfd4e15b11 -config_hash: 47b922b06d16fe0e7595c5eacbbb5495 -codegen_sha: 5228c89d4e6bd4c1e5eca339d0116d6b40ecbdf1 -codegen_hash: f18269d01828f09deac5c4ac6561a04f03fd7dbc6281f596cdb1ab91074e8560 -public_codegen_sha: 81d6121e965a7884690bcfb26b6f5859a1ed106b +generation_id: fb116796-2ea5-4163-b68f-b61bf0010915 +openapi_spec_hash: e81fa0fd2133715dabef9f00a42557a9 +openapi_transformed_spec_hash: bea7a30d23deed3fc8f1c6413a38d354 +config_hash: 77036ac910eb540010c265da4ea569fa +codegen_sha: fb779116bbfc76b8edaca001ad8b4b91caaab006 +codegen_hash: ba4243ffb54f4298c746fa063ec51f6945b7057f9ca92e2743803b4ee9a624da +public_codegen_sha: 1a345e61a29b52a1329b349e89df7e076c9b3514 diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index 4e7b57bc4..ed9a47e02 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -50637,6 +50637,7 @@ components: - $ref: '#/components/schemas/LiveInputAudioAppendEvent' - $ref: '#/components/schemas/LiveInputAudioMuteParam' - $ref: '#/components/schemas/LiveInputAudioUnmuteParam' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlayParam' - $ref: '#/components/schemas/LiveInstructionsAppendParam' - $ref: '#/components/schemas/LiveThinkingAppendParam' - $ref: '#/components/schemas/LiveCommentaryAppendParam' @@ -50694,6 +50695,7 @@ components: - $ref: '#/components/schemas/LiveInputAudioAppendEvent' - $ref: '#/components/schemas/LiveInputAudioMuteParam' - $ref: '#/components/schemas/LiveInputAudioUnmuteParam' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlayParam' - $ref: '#/components/schemas/LiveInstructionsAppendParam' - $ref: '#/components/schemas/LiveThinkingAppendParam' - $ref: '#/components/schemas/LiveCommentaryAppendParam' @@ -50881,13 +50883,14 @@ components: - session - transport LiveSidebandClientEvent: - description: Client events accepted by an attached Live sideband WebSocket. The session is already started; send audio over the primary connection. + description: Client events accepted by an attached Live sideband WebSocket. The session has already started. Send input audio over the primary connection. discriminator: propertyName: type anyOf: - $ref: '#/components/schemas/LiveSessionUpdateParam' - $ref: '#/components/schemas/LiveInputAudioMuteParam' - $ref: '#/components/schemas/LiveInputAudioUnmuteParam' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlayParam' - $ref: '#/components/schemas/LiveInstructionsAppendParam' - $ref: '#/components/schemas/LiveThinkingAppendParam' - $ref: '#/components/schemas/LiveCommentaryAppendParam' @@ -50920,6 +50923,8 @@ components: - $ref: '#/components/schemas/LiveSessionUpdated' - $ref: '#/components/schemas/LiveInputAudioMuted' - $ref: '#/components/schemas/LiveInputAudioUnmuted' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlaying' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlayed' - $ref: '#/components/schemas/LiveInstructionsAppended' - $ref: '#/components/schemas/LiveThinkingAppended' - $ref: '#/components/schemas/LiveCommentaryAppended' @@ -92107,6 +92112,39 @@ components: example: type: session.input_audio.unmute event_id: evt_unmute_001 + LiveOutputAudioFixturePlayParam: + description: 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. + example: + type: session.output_audio.fixture.play + event_id: evt_play_001 + audio: AAAAAA== + type: object + properties: + event_id: + anyOf: + - description: Optional client identifier for correlating this command with a server event's client_event_id or error.client_event_id. + type: string + maxLength: 512 + - type: 'null' + type: + default: session.output_audio.fixture.play + x-stainless-const: true + type: string + enum: + - session.output_audio.fixture.play + audio: + description: Base64-encoded raw mono PCM16 audio, little-endian, at 24 kHz. Do not include a WAV or other container header. + type: string + minLength: 1 + maxLength: 1920000 + required: + - type + - audio + x-oaiMeta: + example: + type: session.output_audio.fixture.play + event_id: evt_play_001 + audio: AAAAAA== LiveInstructionsAppendParam: description: Append instructions to the Live conversation while it is running, optionally associating them with an existing client delegation. example: @@ -92624,6 +92662,62 @@ components: type: session.input_audio.unmuted event_id: evt_unmuted_001 client_event_id: evt_unmute_001 + LiveOutputAudioFixturePlaying: + description: 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. + example: + type: session.output_audio.fixture.playing + event_id: evt_playing_001 + client_event_id: evt_play_001 + type: object + properties: + event_id: + description: The unique ID of the Live server event. + type: string + client_event_id: + description: The event_id of the client command associated with this server event, when supplied. + type: string + type: + default: session.output_audio.fixture.playing + x-stainless-const: true + type: string + enum: + - session.output_audio.fixture.playing + required: + - event_id + - type + x-oaiMeta: + example: + type: session.output_audio.fixture.playing + event_id: evt_playing_001 + client_event_id: evt_play_001 + LiveOutputAudioFixturePlayed: + description: 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. + example: + type: session.output_audio.fixture.played + event_id: evt_played_001 + client_event_id: evt_play_001 + type: object + properties: + event_id: + description: The unique ID of the Live server event. + type: string + client_event_id: + description: The event_id of the client command associated with this server event, when supplied. + type: string + type: + default: session.output_audio.fixture.played + x-stainless-const: true + type: string + enum: + - session.output_audio.fixture.played + required: + - event_id + - type + x-oaiMeta: + example: + type: session.output_audio.fixture.played + event_id: evt_played_001 + client_event_id: evt_play_001 LiveInstructionsAppended: description: 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. example: @@ -93487,6 +93581,8 @@ components: - $ref: '#/components/schemas/LiveSessionUpdated' - $ref: '#/components/schemas/LiveInputAudioMuted' - $ref: '#/components/schemas/LiveInputAudioUnmuted' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlaying' + - $ref: '#/components/schemas/LiveOutputAudioFixturePlayed' - $ref: '#/components/schemas/LiveInstructionsAppended' - $ref: '#/components/schemas/LiveThinkingAppended' - $ref: '#/components/schemas/LiveCommentaryAppended' @@ -107960,6 +108056,9 @@ x-oaiMeta: - type: object key: LiveInputAudioUnmuteParam path: + - type: object + key: LiveOutputAudioFixturePlayParam + path: - type: object key: LiveInstructionsAppendParam path: @@ -108004,6 +108103,12 @@ x-oaiMeta: - type: object key: LiveInputAudioUnmuted path: + - type: object + key: LiveOutputAudioFixturePlaying + path: + - type: object + key: LiveOutputAudioFixturePlayed + path: - type: object key: LiveInstructionsAppended path: diff --git a/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LargePayloadContractTest.kt b/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LargePayloadContractTest.kt index 14594a24f..bc40c6c76 100644 --- a/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LargePayloadContractTest.kt +++ b/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LargePayloadContractTest.kt @@ -1,11 +1,19 @@ package com.openai.client.okhttp +import com.openai.core.ClientOptions +import com.openai.core.jsonMapper import com.openai.helpers.ChatCompletionAccumulator +import com.openai.helpers.LiveSidebandConnection import com.openai.helpers.ResponseAccumulator import com.openai.models.chat.completions.ChatCompletionCreateParams +import com.openai.models.live.OutputAudioFixturePlayEvent import com.openai.models.responses.Response import com.openai.models.responses.ResponseCreateParams +import java.util.Base64 +import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit +import okhttp3.WebSocket +import okhttp3.WebSocketListener import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.assertj.core.api.Assertions.assertThat @@ -23,6 +31,40 @@ internal class LargePayloadContractTest { // Generate data in memory and keep these few cases sequential. private val payloadSize = 16 * 1024 * 1024 + 1 + @Test + fun liveSidebandSendsMaximumAudioFixture() { + // The maximum is 30 seconds of 24 kHz mono PCM16: 1,440,000 raw bytes. + val audio = Base64.getEncoder().encodeToString(ByteArray(30 * 24_000 * 2)) + val received = CompletableFuture() + MockWebServer().use { server -> + server.enqueue( + MockResponse() + .withWebSocketUpgrade( + object : WebSocketListener() { + override fun onMessage(webSocket: WebSocket, text: String) { + received.complete(text) + } + } + ) + ) + OkHttpClient.builder().build().use { http -> + val options = + ClientOptions.builder() + .httpClient(http) + .apiKey("fake-key") + .baseUrl(server.url("/v1").toString()) + .build() + LiveSidebandConnection.connect(options, "fixture-session").use { sideband -> + sideband.send(OutputAudioFixturePlayEvent.builder().audio(audio).build()) + val event = jsonMapper().readTree(received.get(10, TimeUnit.SECONDS)) + assertThat(event.path("type").asText()) + .isEqualTo("session.output_audio.fixture.play") + assertCompleteText(event.path("audio").asText(), audio) + } + } + } + } + @Test fun blockingResponsesJsonPreservesLargeOutputText() { val text = "x".repeat(payloadSize) diff --git a/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LiveSidebandConnectionTest.kt b/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LiveSidebandConnectionTest.kt index f9e59fd1b..331b217d1 100644 --- a/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LiveSidebandConnectionTest.kt +++ b/openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/LiveSidebandConnectionTest.kt @@ -14,6 +14,7 @@ import com.openai.helpers.LiveSidebandConnection import com.openai.helpers.LiveWebSocketOptions import com.openai.models.live.InputAudioMuteEvent import com.openai.models.live.InputAudioUnmuteEvent +import com.openai.models.live.OutputAudioFixturePlayEvent import com.openai.models.live.SessionCloseEvent import java.io.IOException import java.net.URI @@ -180,6 +181,63 @@ class LiveSidebandConnectionTest { } } + @Test + fun outputAudioFixturePlaybackLifecycleOverSideband() { + MockWebServer().use { server -> + val peer = Peer() + server.enqueue(MockResponse().withWebSocketUpgrade(peer)) + OkHttpClient.builder().build().use { http -> + LiveSidebandConnection.connect(options(server, http), "signaling").use { sideband -> + val fixture = + OutputAudioFixturePlayEvent.builder() + .audio("AAABAA==") + .eventId("fixture-1") + .build() + assertThatThrownBy { + sideband.send( + fixture + .toBuilder() + .type(JsonValue.from("session.input_audio.append")) + .build() + ) + } + .isInstanceOf(IllegalArgumentException::class.java) + sideband.send(fixture) + val play = mapper.readTree(peer.messages.poll(8, TimeUnit.SECONDS)) + assertThat(play) + .isEqualTo( + mapper.readTree( + """{"type":"session.output_audio.fixture.play","audio":"AAABAA==","event_id":"fixture-1"}""" + ) + ) + val clientEventId = play.path("event_id").asText() + peer.socket.get(8, TimeUnit.SECONDS).apply { + send( + """{"type":"session.output_audio.fixture.playing","event_id":"playing-1","client_event_id":"$clientEventId"}""" + ) + send( + """{"type":"session.output_audio.fixture.played","event_id":"played-1","client_event_id":"$clientEventId"}""" + ) + } + val playing = + sideband + .receiveAsync() + .get(8, TimeUnit.SECONDS) + .asSessionOutputAudioFixturePlaying() + assertThat(playing.eventId()).isEqualTo("playing-1") + assertThat(playing.clientEventId()).contains(clientEventId) + val played = + sideband + .receiveAsync() + .get(8, TimeUnit.SECONDS) + .asSessionOutputAudioFixturePlayed() + assertThat(played.eventId()).isEqualTo("played-1") + assertThat(played.clientEventId()).contains(clientEventId) + } + } + } + } + @Test fun explicitSessionCloseDrainsErrorsAndFutureEventsWhileObserverCloseOnlyDetaches() { MockWebServer().use { server -> 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 1a2bafd12..59c190ae7 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 @@ -9,6 +9,7 @@ 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.ServerEvent @@ -22,7 +23,8 @@ import java.util.concurrent.CompletableFuture * credentials. Configure the intended bearer in ClientOptions or per-connection headers; matching a * project alone does not guarantee access. A completed connection is transport-open, not a fresh * session.started: the server can replay recent events followed by live delivery. No session.start, - * reconnect, or audio append is sent by this helper. Send audio over the primary transport. + * reconnect, or input audio append is sent by this helper. Send input audio over the primary + * transport. * * Receive typed Live server events (including reflected audio and signaling events) in wire order. * Closing the observer releases its transport without ending the underlying session or its client. @@ -93,6 +95,8 @@ private constructor( fun send(event: InputAudioUnmuteEvent) = sendCommand(event) + fun send(event: OutputAudioFixturePlayEvent) = sendCommand(event) + fun send(event: InstructionsAppendEvent) = sendCommand(event) fun send(event: ThinkingAppendEvent) = sendCommand(event) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/ClientEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/ClientEvent.kt index 47d9fd199..f8b178590 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/live/ClientEvent.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/ClientEvent.kt @@ -34,6 +34,7 @@ private constructor( private val sessionInputAudioAppend: InputAudioAppendEvent? = 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, @@ -76,6 +77,18 @@ private constructor( 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. @@ -126,6 +139,8 @@ private constructor( fun isSessionInputAudioUnmute(): Boolean = sessionInputAudioUnmute != null + fun isSessionOutputAudioFixturePlay(): Boolean = sessionOutputAudioFixturePlay != null + fun isSessionInstructionsAppend(): Boolean = sessionInstructionsAppend != null fun isSessionThinkingAppend(): Boolean = sessionThinkingAppend != null @@ -171,6 +186,18 @@ private constructor( 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. @@ -252,6 +279,8 @@ private constructor( visitor.visitSessionInputAudioMute(sessionInputAudioMute) sessionInputAudioUnmute != null -> visitor.visitSessionInputAudioUnmute(sessionInputAudioUnmute) + sessionOutputAudioFixturePlay != null -> + visitor.visitSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) sessionInstructionsAppend != null -> visitor.visitSessionInstructionsAppend(sessionInstructionsAppend) sessionThinkingAppend != null -> @@ -307,6 +336,12 @@ private constructor( sessionInputAudioUnmute.validate() } + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) { + sessionOutputAudioFixturePlay.validate() + } + override fun visitSessionInstructionsAppend( sessionInstructionsAppend: InstructionsAppendEvent ) { @@ -376,6 +411,10 @@ private constructor( sessionInputAudioUnmute: InputAudioUnmuteEvent ) = sessionInputAudioUnmute.validity() + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) = sessionOutputAudioFixturePlay.validity() + override fun visitSessionInstructionsAppend( sessionInstructionsAppend: InstructionsAppendEvent ) = sessionInstructionsAppend.validity() @@ -412,6 +451,7 @@ private constructor( sessionInputAudioAppend == other.sessionInputAudioAppend && sessionInputAudioMute == other.sessionInputAudioMute && sessionInputAudioUnmute == other.sessionInputAudioUnmute && + sessionOutputAudioFixturePlay == other.sessionOutputAudioFixturePlay && sessionInstructionsAppend == other.sessionInstructionsAppend && sessionThinkingAppend == other.sessionThinkingAppend && sessionCommentaryAppend == other.sessionCommentaryAppend && @@ -427,6 +467,7 @@ private constructor( sessionInputAudioAppend, sessionInputAudioMute, sessionInputAudioUnmute, + sessionOutputAudioFixturePlay, sessionInstructionsAppend, sessionThinkingAppend, sessionCommentaryAppend, @@ -445,6 +486,8 @@ private constructor( "ClientEvent{sessionInputAudioMute=$sessionInputAudioMute}" sessionInputAudioUnmute != null -> "ClientEvent{sessionInputAudioUnmute=$sessionInputAudioUnmute}" + sessionOutputAudioFixturePlay != null -> + "ClientEvent{sessionOutputAudioFixturePlay=$sessionOutputAudioFixturePlay}" sessionInstructionsAppend != null -> "ClientEvent{sessionInstructionsAppend=$sessionInstructionsAppend}" sessionThinkingAppend != null -> @@ -500,6 +543,20 @@ private constructor( fun ofSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent) = ClientEvent(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 + ) = ClientEvent(sessionOutputAudioFixturePlay = sessionOutputAudioFixturePlay) + /** * Append instructions to the Live conversation while it is running, optionally associating * them with an existing client delegation. @@ -558,67 +615,91 @@ private constructor( * Start a Live session on a primary WebSocket. Send this event before other commands and * wait for `session.started`. */ - fun visitSessionStart(sessionStart: SessionStartEvent): T + fun visitSessionStart(sessionStart: SessionStartEvent): T = + unknown(JsonValue.from(sessionStart)) /** * Update the delegation settings of an active Live session. The server acknowledges * accepted changes with `session.updated`. */ - fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent): T + fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent): T = + unknown(JsonValue.from(sessionUpdate)) /** * Send audio to a Live session over its primary WebSocket. WebRTC and SIP sessions send * audio over their media transport. */ - fun visitSessionInputAudioAppend(sessionInputAudioAppend: InputAudioAppendEvent): T + fun visitSessionInputAudioAppend(sessionInputAudioAppend: InputAudioAppendEvent): T = + unknown(JsonValue.from(sessionInputAudioAppend)) /** * Mute audio input to the Live model without closing the session. The server acknowledges * with `session.input_audio.muted`. */ - fun visitSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent): T + fun visitSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent): T = + unknown(JsonValue.from(sessionInputAudioMute)) /** * Resume audio input to a Live model after muting it. The server acknowledges with * `session.input_audio.unmuted`. */ - fun visitSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent): T + fun visitSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent): T = + unknown(JsonValue.from(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 visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ): T = unknown(JsonValue.from(sessionOutputAudioFixturePlay)) /** * Append instructions to the Live conversation while it is running, optionally associating * them with an existing client delegation. */ - fun visitSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent): T + fun visitSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent): T = + unknown(JsonValue.from(sessionInstructionsAppend)) /** * Provide silent reasoning or progress context to the Live model, optionally for an * existing client delegation. */ - fun visitSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent): T + fun visitSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent): T = + unknown(JsonValue.from(sessionThinkingAppend)) /** * Provide context the Live model can communicate to the user, optionally for an existing * client delegation. */ - fun visitSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent): T + fun visitSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent): T = + unknown(JsonValue.from(sessionCommentaryAppend)) /** * 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 + fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent): T = + unknown(JsonValue.from(responseItemCreate)) /** * 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 + fun visitResponseCreate(responseCreate: ResponseCreateEvent): T = + unknown(JsonValue.from(responseCreate)) /** * Request that the Live session close. The terminal `session.closed` event contains the * close reason and final usage. */ - fun visitSessionClose(sessionClose: SessionCloseEvent): T + fun visitSessionClose(sessionClose: SessionCloseEvent): T = + unknown(JsonValue.from(sessionClose)) /** * Maps an unknown variant of [ClientEvent] to a value of type [T]. @@ -627,6 +708,9 @@ private constructor( * 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 { @@ -666,6 +750,11 @@ private constructor( ClientEvent(sessionInputAudioUnmute = it, _json = json) } ?: ClientEvent(_json = json) } + "session.output_audio.fixture.play" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { ClientEvent(sessionOutputAudioFixturePlay = it, _json = json) } + ?: ClientEvent(_json = json) + } "session.instructions.append" -> { return tryDeserialize(node, jacksonTypeRef())?.let { ClientEvent(sessionInstructionsAppend = it, _json = json) @@ -718,6 +807,8 @@ private constructor( 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 -> diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayEvent.kt new file mode 100644 index 000000000..016060145 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayEvent.kt @@ -0,0 +1,275 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.openai.core.ExcludeMissing +import com.openai.core.JsonField +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.checkRequired +import com.openai.errors.OpenAIInvalidDataException +import java.util.Collections +import java.util.Objects +import java.util.Optional +import kotlin.jvm.optionals.getOrNull + +/** + * 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. + */ +class OutputAudioFixturePlayEvent +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val audio: JsonField, + private val type: JsonValue, + private val eventId: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("audio") @ExcludeMissing audio: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonValue = JsonMissing.of(), + @JsonProperty("event_id") @ExcludeMissing eventId: JsonField = JsonMissing.of(), + ) : this(audio, type, eventId, mutableMapOf()) + + /** + * Base64-encoded raw mono PCM16 audio, little-endian, at 24 kHz. Do not include a WAV or other + * container header. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun audio(): String = audio.getRequired("audio") + + /** + * Expected to always return the following: + * ```java + * JsonValue.from("session.output_audio.fixture.play") + * ``` + * + * However, this method can be useful for debugging and logging (e.g. if the server responded + * with an unexpected value). + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonValue = type + + /** + * Optional client identifier for correlating this command with a server event's client_event_id + * or error.client_event_id. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun eventId(): Optional = eventId.getOptional("event_id") + + /** + * Returns the raw JSON value of [audio]. + * + * Unlike [audio], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("audio") @ExcludeMissing fun _audio(): JsonField = audio + + /** + * Returns the raw JSON value of [eventId]. + * + * Unlike [eventId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("event_id") @ExcludeMissing fun _eventId(): JsonField = eventId + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of [OutputAudioFixturePlayEvent]. + * + * The following fields are required: + * ```java + * .audio() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [OutputAudioFixturePlayEvent]. */ + class Builder internal constructor() { + + private var audio: JsonField? = null + private var type: JsonValue = JsonValue.from("session.output_audio.fixture.play") + private var eventId: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(outputAudioFixturePlayEvent: OutputAudioFixturePlayEvent) = apply { + audio = outputAudioFixturePlayEvent.audio + type = outputAudioFixturePlayEvent.type + eventId = outputAudioFixturePlayEvent.eventId + additionalProperties = outputAudioFixturePlayEvent.additionalProperties.toMutableMap() + } + + /** + * Base64-encoded raw mono PCM16 audio, little-endian, at 24 kHz. Do not include a WAV or + * other container header. + */ + fun audio(audio: String) = audio(JsonField.of(audio)) + + /** + * Sets [Builder.audio] to an arbitrary JSON value. + * + * You should usually call [Builder.audio] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun audio(audio: JsonField) = apply { this.audio = audio } + + /** + * Sets the field to an arbitrary JSON value. + * + * It is usually unnecessary to call this method because the field defaults to the + * following: + * ```java + * JsonValue.from("session.output_audio.fixture.play") + * ``` + * + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun type(type: JsonValue) = apply { this.type = type } + + /** + * Optional client identifier for correlating this command with a server event's + * client_event_id or error.client_event_id. + */ + fun eventId(eventId: String?) = eventId(JsonField.ofNullable(eventId)) + + /** Alias for calling [Builder.eventId] with `eventId.orElse(null)`. */ + fun eventId(eventId: Optional) = eventId(eventId.getOrNull()) + + /** + * Sets [Builder.eventId] to an arbitrary JSON value. + * + * You should usually call [Builder.eventId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun eventId(eventId: JsonField) = apply { this.eventId = eventId } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [OutputAudioFixturePlayEvent]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .audio() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): OutputAudioFixturePlayEvent = + OutputAudioFixturePlayEvent( + checkRequired("audio", audio), + type, + eventId, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws OpenAIInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): OutputAudioFixturePlayEvent = apply { + if (validated) { + return@apply + } + + audio() + _type().let { + if (it != JsonValue.from("session.output_audio.fixture.play")) { + throw OpenAIInvalidDataException("'type' is invalid, received $it") + } + } + eventId() + 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 = + (if (audio.asKnown().isPresent) 1 else 0) + + type.let { if (it == JsonValue.from("session.output_audio.fixture.play")) 1 else 0 } + + (if (eventId.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is OutputAudioFixturePlayEvent && + audio == other.audio && + type == other.type && + eventId == other.eventId && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { Objects.hash(audio, type, eventId, additionalProperties) } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "OutputAudioFixturePlayEvent{audio=$audio, type=$type, eventId=$eventId, additionalProperties=$additionalProperties}" +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayedEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayedEvent.kt new file mode 100644 index 000000000..4e521f590 --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayedEvent.kt @@ -0,0 +1,271 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.openai.core.ExcludeMissing +import com.openai.core.JsonField +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.checkRequired +import com.openai.errors.OpenAIInvalidDataException +import java.util.Collections +import java.util.Objects +import java.util.Optional + +/** + * 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. + */ +class OutputAudioFixturePlayedEvent +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val eventId: JsonField, + private val type: JsonValue, + private val clientEventId: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("event_id") @ExcludeMissing eventId: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonValue = JsonMissing.of(), + @JsonProperty("client_event_id") + @ExcludeMissing + clientEventId: JsonField = JsonMissing.of(), + ) : this(eventId, type, clientEventId, mutableMapOf()) + + /** + * The unique ID of the Live server event. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun eventId(): String = eventId.getRequired("event_id") + + /** + * Expected to always return the following: + * ```java + * JsonValue.from("session.output_audio.fixture.played") + * ``` + * + * However, this method can be useful for debugging and logging (e.g. if the server responded + * with an unexpected value). + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonValue = type + + /** + * The event_id of the client command associated with this server event, when supplied. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun clientEventId(): Optional = clientEventId.getOptional("client_event_id") + + /** + * Returns the raw JSON value of [eventId]. + * + * Unlike [eventId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("event_id") @ExcludeMissing fun _eventId(): JsonField = eventId + + /** + * Returns the raw JSON value of [clientEventId]. + * + * Unlike [clientEventId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("client_event_id") + @ExcludeMissing + fun _clientEventId(): JsonField = clientEventId + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of + * [OutputAudioFixturePlayedEvent]. + * + * The following fields are required: + * ```java + * .eventId() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [OutputAudioFixturePlayedEvent]. */ + class Builder internal constructor() { + + private var eventId: JsonField? = null + private var type: JsonValue = JsonValue.from("session.output_audio.fixture.played") + private var clientEventId: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(outputAudioFixturePlayedEvent: OutputAudioFixturePlayedEvent) = apply { + eventId = outputAudioFixturePlayedEvent.eventId + type = outputAudioFixturePlayedEvent.type + clientEventId = outputAudioFixturePlayedEvent.clientEventId + additionalProperties = outputAudioFixturePlayedEvent.additionalProperties.toMutableMap() + } + + /** The unique ID of the Live server event. */ + fun eventId(eventId: String) = eventId(JsonField.of(eventId)) + + /** + * Sets [Builder.eventId] to an arbitrary JSON value. + * + * You should usually call [Builder.eventId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun eventId(eventId: JsonField) = apply { this.eventId = eventId } + + /** + * Sets the field to an arbitrary JSON value. + * + * It is usually unnecessary to call this method because the field defaults to the + * following: + * ```java + * JsonValue.from("session.output_audio.fixture.played") + * ``` + * + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun type(type: JsonValue) = apply { this.type = type } + + /** The event_id of the client command associated with this server event, when supplied. */ + fun clientEventId(clientEventId: String) = clientEventId(JsonField.of(clientEventId)) + + /** + * Sets [Builder.clientEventId] to an arbitrary JSON value. + * + * You should usually call [Builder.clientEventId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun clientEventId(clientEventId: JsonField) = apply { + this.clientEventId = clientEventId + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [OutputAudioFixturePlayedEvent]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .eventId() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): OutputAudioFixturePlayedEvent = + OutputAudioFixturePlayedEvent( + checkRequired("eventId", eventId), + type, + clientEventId, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws OpenAIInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): OutputAudioFixturePlayedEvent = apply { + if (validated) { + return@apply + } + + eventId() + _type().let { + if (it != JsonValue.from("session.output_audio.fixture.played")) { + throw OpenAIInvalidDataException("'type' is invalid, received $it") + } + } + clientEventId() + 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 = + (if (eventId.asKnown().isPresent) 1 else 0) + + type.let { if (it == JsonValue.from("session.output_audio.fixture.played")) 1 else 0 } + + (if (clientEventId.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is OutputAudioFixturePlayedEvent && + eventId == other.eventId && + type == other.type && + clientEventId == other.clientEventId && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(eventId, type, clientEventId, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "OutputAudioFixturePlayedEvent{eventId=$eventId, type=$type, clientEventId=$clientEventId, additionalProperties=$additionalProperties}" +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayingEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayingEvent.kt new file mode 100644 index 000000000..084f761cd --- /dev/null +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/OutputAudioFixturePlayingEvent.kt @@ -0,0 +1,273 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.annotation.JsonAnyGetter +import com.fasterxml.jackson.annotation.JsonAnySetter +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonProperty +import com.openai.core.ExcludeMissing +import com.openai.core.JsonField +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.checkRequired +import com.openai.errors.OpenAIInvalidDataException +import java.util.Collections +import java.util.Objects +import java.util.Optional + +/** + * 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. + */ +class OutputAudioFixturePlayingEvent +@JsonCreator(mode = JsonCreator.Mode.DISABLED) +private constructor( + private val eventId: JsonField, + private val type: JsonValue, + private val clientEventId: JsonField, + private val additionalProperties: MutableMap, +) { + + @JsonCreator + private constructor( + @JsonProperty("event_id") @ExcludeMissing eventId: JsonField = JsonMissing.of(), + @JsonProperty("type") @ExcludeMissing type: JsonValue = JsonMissing.of(), + @JsonProperty("client_event_id") + @ExcludeMissing + clientEventId: JsonField = JsonMissing.of(), + ) : this(eventId, type, clientEventId, mutableMapOf()) + + /** + * The unique ID of the Live server event. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type or is + * unexpectedly missing or null (e.g. if the server responded with an unexpected value). + */ + fun eventId(): String = eventId.getRequired("event_id") + + /** + * Expected to always return the following: + * ```java + * JsonValue.from("session.output_audio.fixture.playing") + * ``` + * + * However, this method can be useful for debugging and logging (e.g. if the server responded + * with an unexpected value). + */ + @JsonProperty("type") @ExcludeMissing fun _type(): JsonValue = type + + /** + * The event_id of the client command associated with this server event, when supplied. + * + * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the + * server responded with an unexpected value). + */ + fun clientEventId(): Optional = clientEventId.getOptional("client_event_id") + + /** + * Returns the raw JSON value of [eventId]. + * + * Unlike [eventId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("event_id") @ExcludeMissing fun _eventId(): JsonField = eventId + + /** + * Returns the raw JSON value of [clientEventId]. + * + * Unlike [clientEventId], this method doesn't throw if the JSON field has an unexpected type. + */ + @JsonProperty("client_event_id") + @ExcludeMissing + fun _clientEventId(): JsonField = clientEventId + + @JsonAnySetter + private fun putAdditionalProperty(key: String, value: JsonValue) { + additionalProperties.put(key, value) + } + + @JsonAnyGetter + @ExcludeMissing + fun _additionalProperties(): Map = + Collections.unmodifiableMap(additionalProperties) + + fun toBuilder() = Builder().from(this) + + companion object { + + /** + * Returns a mutable builder for constructing an instance of + * [OutputAudioFixturePlayingEvent]. + * + * The following fields are required: + * ```java + * .eventId() + * ``` + */ + @JvmStatic fun builder() = Builder() + } + + /** A builder for [OutputAudioFixturePlayingEvent]. */ + class Builder internal constructor() { + + private var eventId: JsonField? = null + private var type: JsonValue = JsonValue.from("session.output_audio.fixture.playing") + private var clientEventId: JsonField = JsonMissing.of() + private var additionalProperties: MutableMap = mutableMapOf() + + @JvmSynthetic + internal fun from(outputAudioFixturePlayingEvent: OutputAudioFixturePlayingEvent) = apply { + eventId = outputAudioFixturePlayingEvent.eventId + type = outputAudioFixturePlayingEvent.type + clientEventId = outputAudioFixturePlayingEvent.clientEventId + additionalProperties = + outputAudioFixturePlayingEvent.additionalProperties.toMutableMap() + } + + /** The unique ID of the Live server event. */ + fun eventId(eventId: String) = eventId(JsonField.of(eventId)) + + /** + * Sets [Builder.eventId] to an arbitrary JSON value. + * + * You should usually call [Builder.eventId] with a well-typed [String] value instead. This + * method is primarily for setting the field to an undocumented or not yet supported value. + */ + fun eventId(eventId: JsonField) = apply { this.eventId = eventId } + + /** + * Sets the field to an arbitrary JSON value. + * + * It is usually unnecessary to call this method because the field defaults to the + * following: + * ```java + * JsonValue.from("session.output_audio.fixture.playing") + * ``` + * + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun type(type: JsonValue) = apply { this.type = type } + + /** The event_id of the client command associated with this server event, when supplied. */ + fun clientEventId(clientEventId: String) = clientEventId(JsonField.of(clientEventId)) + + /** + * Sets [Builder.clientEventId] to an arbitrary JSON value. + * + * You should usually call [Builder.clientEventId] with a well-typed [String] value instead. + * This method is primarily for setting the field to an undocumented or not yet supported + * value. + */ + fun clientEventId(clientEventId: JsonField) = apply { + this.clientEventId = clientEventId + } + + fun additionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.clear() + putAllAdditionalProperties(additionalProperties) + } + + fun putAdditionalProperty(key: String, value: JsonValue) = apply { + additionalProperties.put(key, value) + } + + fun putAllAdditionalProperties(additionalProperties: Map) = apply { + this.additionalProperties.putAll(additionalProperties) + } + + fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) } + + fun removeAllAdditionalProperties(keys: Set) = apply { + keys.forEach(::removeAdditionalProperty) + } + + /** + * Returns an immutable instance of [OutputAudioFixturePlayingEvent]. + * + * Further updates to this [Builder] will not mutate the returned instance. + * + * The following fields are required: + * ```java + * .eventId() + * ``` + * + * @throws IllegalStateException if any required field is unset. + */ + fun build(): OutputAudioFixturePlayingEvent = + OutputAudioFixturePlayingEvent( + checkRequired("eventId", eventId), + type, + clientEventId, + additionalProperties.toMutableMap(), + ) + } + + private var validated: Boolean = false + + /** + * Validates that the types of all values in this object match their expected types recursively. + * + * This method is _not_ forwards compatible with new types from the API for existing fields. + * + * @throws OpenAIInvalidDataException if any value type in this object doesn't match its + * expected type. + */ + fun validate(): OutputAudioFixturePlayingEvent = apply { + if (validated) { + return@apply + } + + eventId() + _type().let { + if (it != JsonValue.from("session.output_audio.fixture.playing")) { + throw OpenAIInvalidDataException("'type' is invalid, received $it") + } + } + clientEventId() + 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 = + (if (eventId.asKnown().isPresent) 1 else 0) + + type.let { + if (it == JsonValue.from("session.output_audio.fixture.playing")) 1 else 0 + } + + (if (clientEventId.asKnown().isPresent) 1 else 0) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + return other is OutputAudioFixturePlayingEvent && + eventId == other.eventId && + type == other.type && + clientEventId == other.clientEventId && + additionalProperties == other.additionalProperties + } + + private val hashCode: Int by lazy { + Objects.hash(eventId, type, clientEventId, additionalProperties) + } + + override fun hashCode(): Int = hashCode + + override fun toString() = + "OutputAudioFixturePlayingEvent{eventId=$eventId, type=$type, clientEventId=$clientEventId, additionalProperties=$additionalProperties}" +} diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/ServerEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/ServerEvent.kt index ea80054cf..8d39078bf 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/live/ServerEvent.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/ServerEvent.kt @@ -41,6 +41,8 @@ private constructor( 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, @@ -88,6 +90,23 @@ private constructor( 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 @@ -213,6 +232,10 @@ private constructor( 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 @@ -275,6 +298,23 @@ private constructor( 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 @@ -431,6 +471,10 @@ private constructor( 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 -> @@ -498,6 +542,18 @@ private constructor( sessionInputAudioUnmuted.validate() } + override fun visitSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ) { + sessionOutputAudioFixturePlaying.validate() + } + + override fun visitSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ) { + sessionOutputAudioFixturePlayed.validate() + } + override fun visitSessionInstructionsAppended( sessionInstructionsAppended: InstructionsAppendedEvent ) { @@ -625,6 +681,14 @@ private constructor( 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() @@ -701,6 +765,8 @@ private constructor( sessionUpdated == other.sessionUpdated && sessionInputAudioMuted == other.sessionInputAudioMuted && sessionInputAudioUnmuted == other.sessionInputAudioUnmuted && + sessionOutputAudioFixturePlaying == other.sessionOutputAudioFixturePlaying && + sessionOutputAudioFixturePlayed == other.sessionOutputAudioFixturePlayed && sessionInstructionsAppended == other.sessionInstructionsAppended && sessionThinkingAppended == other.sessionThinkingAppended && sessionCommentaryAppended == other.sessionCommentaryAppended && @@ -727,6 +793,8 @@ private constructor( sessionUpdated, sessionInputAudioMuted, sessionInputAudioUnmuted, + sessionOutputAudioFixturePlaying, + sessionOutputAudioFixturePlayed, sessionInstructionsAppended, sessionThinkingAppended, sessionCommentaryAppended, @@ -755,6 +823,10 @@ private constructor( "ServerEvent{sessionInputAudioMuted=$sessionInputAudioMuted}" sessionInputAudioUnmuted != null -> "ServerEvent{sessionInputAudioUnmuted=$sessionInputAudioUnmuted}" + sessionOutputAudioFixturePlaying != null -> + "ServerEvent{sessionOutputAudioFixturePlaying=$sessionOutputAudioFixturePlaying}" + sessionOutputAudioFixturePlayed != null -> + "ServerEvent{sessionOutputAudioFixturePlayed=$sessionOutputAudioFixturePlayed}" sessionInstructionsAppended != null -> "ServerEvent{sessionInstructionsAppended=$sessionInstructionsAppended}" sessionThinkingAppended != null -> @@ -820,6 +892,27 @@ private constructor( fun ofSessionInputAudioUnmuted(sessionInputAudioUnmuted: InputAudioUnmutedEvent) = ServerEvent(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 + ) = ServerEvent(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 + ) = ServerEvent(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 @@ -973,25 +1066,48 @@ private constructor( * Returned when a Live session has started. Contains the resolved session configuration, * including server defaults. */ - fun visitSessionStarted(sessionStarted: SessionStartedEvent): T + 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 + 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 + 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 + 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 @@ -1000,34 +1116,38 @@ private constructor( */ fun visitSessionInstructionsAppended( sessionInstructionsAppended: InstructionsAppendedEvent - ): T + ): 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 + 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 + fun visitSessionCommentaryAppended(sessionCommentaryAppended: CommentaryAppendedEvent): T = + unknown(JsonValue.from(sessionCommentaryAppended)) /** * Input audio received from the primary transport and reflected to a Live sideband * connection before model-input muting. */ - fun visitSessionInputAudioAppend(sessionInputAudioAppend: SessionInputAudioAppend): T + fun visitSessionInputAudioAppend(sessionInputAudioAppend: SessionInputAudioAppend): T = + unknown(JsonValue.from(sessionInputAudioAppend)) /** * An audio chunk generated by the Live model. Decode and play primary WebSocket chunks in * delivery order using the configured session audio format. Sideband connections receive * reflected output audio with timestamps. */ - fun visitSessionOutputAudioDelta(sessionOutputAudioDelta: OutputAudioDeltaEvent): T + fun visitSessionOutputAudioDelta(sessionOutputAudioDelta: OutputAudioDeltaEvent): T = + unknown(JsonValue.from(sessionOutputAudioDelta)) /** * A transcript fragment for user input audio in the Live session. Accumulate fragments in @@ -1036,7 +1156,7 @@ private constructor( */ fun visitSessionInputTranscriptDelta( sessionInputTranscriptDelta: InputTranscriptDeltaEvent - ): T + ): T = unknown(JsonValue.from(sessionInputTranscriptDelta)) /** * A transcript fragment for assistant output audio in the Live session. Accumulate @@ -1045,69 +1165,78 @@ private constructor( */ fun visitSessionOutputTranscriptDelta( sessionOutputTranscriptDelta: OutputTranscriptDeltaEvent - ): T + ): 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 + 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 + 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 + 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 + 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 + 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 + fun visitInfoEvent(infoEvent: InfoEvent): T = unknown(JsonValue.from(infoEvent)) /** A SIP DTMF keypress received from the caller. Delivered only to sideband observers. */ - fun visitTransportDtmfReceived(transportDtmfReceived: TransportDtmfReceived): T + fun visitTransportDtmfReceived(transportDtmfReceived: TransportDtmfReceived): T = + unknown(JsonValue.from(transportDtmfReceived)) /** * A SIP DTMF keypress successfully sent to the SIP trunk. Delivered only to sideband * observers; this is not a client command. */ - fun visitTransportDtmfSend(transportDtmfSend: TransportDtmfSend): T + fun visitTransportDtmfSend(transportDtmfSend: TransportDtmfSend): T = + unknown(JsonValue.from(transportDtmfSend)) /** * The outbound SIP provider leg is ringing or providing early media. Delivered only to * sideband observers. */ - fun visitTransportRinging(transportRinging: TransportRinging): T + fun visitTransportRinging(transportRinging: TransportRinging): T = + unknown(JsonValue.from(transportRinging)) /** * The outbound SIP provider leg answered and media is established. Delivered only to * sideband observers. */ - fun visitTransportAnswered(transportAnswered: TransportAnswered): T + fun visitTransportAnswered(transportAnswered: TransportAnswered): T = + unknown(JsonValue.from(transportAnswered)) /** An asynchronous outbound SIP setup failure. Delivered only to sideband observers. */ - fun visitTransportFailed(transportFailed: TransportFailed): T + fun visitTransportFailed(transportFailed: TransportFailed): T = + unknown(JsonValue.from(transportFailed)) /** * Maps an unknown variant of [ServerEvent] to a value of type [T]. @@ -1116,6 +1245,9 @@ private constructor( * 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 { @@ -1150,6 +1282,16 @@ private constructor( ServerEvent(sessionInputAudioUnmuted = it, _json = json) } ?: ServerEvent(_json = json) } + "session.output_audio.fixture.playing" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { ServerEvent(sessionOutputAudioFixturePlaying = it, _json = json) } + ?: ServerEvent(_json = json) + } + "session.output_audio.fixture.played" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { ServerEvent(sessionOutputAudioFixturePlayed = it, _json = json) } + ?: ServerEvent(_json = json) + } "session.instructions.appended" -> { return tryDeserialize(node, jacksonTypeRef())?.let { ServerEvent(sessionInstructionsAppended = it, _json = json) @@ -1260,6 +1402,10 @@ private constructor( 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 -> diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkClientEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkClientEvent.kt index 876be594e..4424c134d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkClientEvent.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkClientEvent.kt @@ -20,6 +20,7 @@ import com.openai.models.live.InputAudioAppendEvent 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 @@ -43,6 +44,7 @@ private constructor( private val sessionInputAudioAppend: InputAudioAppendEvent? = 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, @@ -85,6 +87,18 @@ private constructor( 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. @@ -135,6 +149,8 @@ private constructor( fun isSessionInputAudioUnmute(): Boolean = sessionInputAudioUnmute != null + fun isSessionOutputAudioFixturePlay(): Boolean = sessionOutputAudioFixturePlay != null + fun isSessionInstructionsAppend(): Boolean = sessionInstructionsAppend != null fun isSessionThinkingAppend(): Boolean = sessionThinkingAppend != null @@ -180,6 +196,18 @@ private constructor( 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. @@ -261,6 +289,8 @@ private constructor( visitor.visitSessionInputAudioMute(sessionInputAudioMute) sessionInputAudioUnmute != null -> visitor.visitSessionInputAudioUnmute(sessionInputAudioUnmute) + sessionOutputAudioFixturePlay != null -> + visitor.visitSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) sessionInstructionsAppend != null -> visitor.visitSessionInstructionsAppend(sessionInstructionsAppend) sessionThinkingAppend != null -> @@ -316,6 +346,12 @@ private constructor( sessionInputAudioUnmute.validate() } + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) { + sessionOutputAudioFixturePlay.validate() + } + override fun visitSessionInstructionsAppend( sessionInstructionsAppend: InstructionsAppendEvent ) { @@ -385,6 +421,10 @@ private constructor( sessionInputAudioUnmute: InputAudioUnmuteEvent ) = sessionInputAudioUnmute.validity() + override fun visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ) = sessionOutputAudioFixturePlay.validity() + override fun visitSessionInstructionsAppend( sessionInstructionsAppend: InstructionsAppendEvent ) = sessionInstructionsAppend.validity() @@ -421,6 +461,7 @@ private constructor( sessionInputAudioAppend == other.sessionInputAudioAppend && sessionInputAudioMute == other.sessionInputAudioMute && sessionInputAudioUnmute == other.sessionInputAudioUnmute && + sessionOutputAudioFixturePlay == other.sessionOutputAudioFixturePlay && sessionInstructionsAppend == other.sessionInstructionsAppend && sessionThinkingAppend == other.sessionThinkingAppend && sessionCommentaryAppend == other.sessionCommentaryAppend && @@ -436,6 +477,7 @@ private constructor( sessionInputAudioAppend, sessionInputAudioMute, sessionInputAudioUnmute, + sessionOutputAudioFixturePlay, sessionInstructionsAppend, sessionThinkingAppend, sessionCommentaryAppend, @@ -454,6 +496,8 @@ private constructor( "ForkClientEvent{sessionInputAudioMute=$sessionInputAudioMute}" sessionInputAudioUnmute != null -> "ForkClientEvent{sessionInputAudioUnmute=$sessionInputAudioUnmute}" + sessionOutputAudioFixturePlay != null -> + "ForkClientEvent{sessionOutputAudioFixturePlay=$sessionOutputAudioFixturePlay}" sessionInstructionsAppend != null -> "ForkClientEvent{sessionInstructionsAppend=$sessionInstructionsAppend}" sessionThinkingAppend != null -> @@ -509,6 +553,20 @@ private constructor( fun ofSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent) = ForkClientEvent(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 + ) = ForkClientEvent(sessionOutputAudioFixturePlay = sessionOutputAudioFixturePlay) + /** * Append instructions to the Live conversation while it is running, optionally associating * them with an existing client delegation. @@ -568,67 +626,91 @@ private constructor( * Start a Live session after connecting to a stored session’s fork WebSocket. Send an empty * `session` object to use the stored configuration. */ - fun visitSessionStart(sessionStart: ForkSessionStartEvent): T + fun visitSessionStart(sessionStart: ForkSessionStartEvent): T = + unknown(JsonValue.from(sessionStart)) /** * Update the delegation settings of an active Live session. The server acknowledges * accepted changes with `session.updated`. */ - fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent): T + fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent): T = + unknown(JsonValue.from(sessionUpdate)) /** * Send audio to a Live session over its primary WebSocket. WebRTC and SIP sessions send * audio over their media transport. */ - fun visitSessionInputAudioAppend(sessionInputAudioAppend: InputAudioAppendEvent): T + fun visitSessionInputAudioAppend(sessionInputAudioAppend: InputAudioAppendEvent): T = + unknown(JsonValue.from(sessionInputAudioAppend)) /** * Mute audio input to the Live model without closing the session. The server acknowledges * with `session.input_audio.muted`. */ - fun visitSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent): T + fun visitSessionInputAudioMute(sessionInputAudioMute: InputAudioMuteEvent): T = + unknown(JsonValue.from(sessionInputAudioMute)) /** * Resume audio input to a Live model after muting it. The server acknowledges with * `session.input_audio.unmuted`. */ - fun visitSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent): T + fun visitSessionInputAudioUnmute(sessionInputAudioUnmute: InputAudioUnmuteEvent): T = + unknown(JsonValue.from(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 visitSessionOutputAudioFixturePlay( + sessionOutputAudioFixturePlay: OutputAudioFixturePlayEvent + ): T = unknown(JsonValue.from(sessionOutputAudioFixturePlay)) /** * Append instructions to the Live conversation while it is running, optionally associating * them with an existing client delegation. */ - fun visitSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent): T + fun visitSessionInstructionsAppend(sessionInstructionsAppend: InstructionsAppendEvent): T = + unknown(JsonValue.from(sessionInstructionsAppend)) /** * Provide silent reasoning or progress context to the Live model, optionally for an * existing client delegation. */ - fun visitSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent): T + fun visitSessionThinkingAppend(sessionThinkingAppend: ThinkingAppendEvent): T = + unknown(JsonValue.from(sessionThinkingAppend)) /** * Provide context the Live model can communicate to the user, optionally for an existing * client delegation. */ - fun visitSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent): T + fun visitSessionCommentaryAppend(sessionCommentaryAppend: CommentaryAppendEvent): T = + unknown(JsonValue.from(sessionCommentaryAppend)) /** * 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 + fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent): T = + unknown(JsonValue.from(responseItemCreate)) /** * 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 + fun visitResponseCreate(responseCreate: ResponseCreateEvent): T = + unknown(JsonValue.from(responseCreate)) /** * Request that the Live session close. The terminal `session.closed` event contains the * close reason and final usage. */ - fun visitSessionClose(sessionClose: SessionCloseEvent): T + fun visitSessionClose(sessionClose: SessionCloseEvent): T = + unknown(JsonValue.from(sessionClose)) /** * Maps an unknown variant of [ForkClientEvent] to a value of type [T]. @@ -638,6 +720,9 @@ private constructor( * 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 { @@ -677,6 +762,11 @@ private constructor( ForkClientEvent(sessionInputAudioUnmute = it, _json = json) } ?: ForkClientEvent(_json = json) } + "session.output_audio.fixture.play" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { ForkClientEvent(sessionOutputAudioFixturePlay = it, _json = json) } + ?: ForkClientEvent(_json = json) + } "session.instructions.append" -> { return tryDeserialize(node, jacksonTypeRef())?.let { ForkClientEvent(sessionInstructionsAppend = it, _json = json) @@ -729,6 +819,8 @@ private constructor( 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 -> diff --git a/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkServerEvent.kt b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkServerEvent.kt index f8176368a..a5c1ad254 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkServerEvent.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/live/forks/ForkServerEvent.kt @@ -31,6 +31,8 @@ import com.openai.models.live.InputAudioUnmutedEvent import com.openai.models.live.InputTranscriptDeltaEvent import com.openai.models.live.InstructionsAppendedEvent import com.openai.models.live.OutputAudioDeltaEvent +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 @@ -57,6 +59,8 @@ private constructor( 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, @@ -104,6 +108,23 @@ private constructor( 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 @@ -229,6 +250,10 @@ private constructor( 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 @@ -291,6 +316,23 @@ private constructor( 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 @@ -447,6 +489,10 @@ private constructor( 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 -> @@ -514,6 +560,18 @@ private constructor( sessionInputAudioUnmuted.validate() } + override fun visitSessionOutputAudioFixturePlaying( + sessionOutputAudioFixturePlaying: OutputAudioFixturePlayingEvent + ) { + sessionOutputAudioFixturePlaying.validate() + } + + override fun visitSessionOutputAudioFixturePlayed( + sessionOutputAudioFixturePlayed: OutputAudioFixturePlayedEvent + ) { + sessionOutputAudioFixturePlayed.validate() + } + override fun visitSessionInstructionsAppended( sessionInstructionsAppended: InstructionsAppendedEvent ) { @@ -641,6 +699,14 @@ private constructor( 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() @@ -717,6 +783,8 @@ private constructor( sessionUpdated == other.sessionUpdated && sessionInputAudioMuted == other.sessionInputAudioMuted && sessionInputAudioUnmuted == other.sessionInputAudioUnmuted && + sessionOutputAudioFixturePlaying == other.sessionOutputAudioFixturePlaying && + sessionOutputAudioFixturePlayed == other.sessionOutputAudioFixturePlayed && sessionInstructionsAppended == other.sessionInstructionsAppended && sessionThinkingAppended == other.sessionThinkingAppended && sessionCommentaryAppended == other.sessionCommentaryAppended && @@ -743,6 +811,8 @@ private constructor( sessionUpdated, sessionInputAudioMuted, sessionInputAudioUnmuted, + sessionOutputAudioFixturePlaying, + sessionOutputAudioFixturePlayed, sessionInstructionsAppended, sessionThinkingAppended, sessionCommentaryAppended, @@ -771,6 +841,10 @@ private constructor( "ForkServerEvent{sessionInputAudioMuted=$sessionInputAudioMuted}" sessionInputAudioUnmuted != null -> "ForkServerEvent{sessionInputAudioUnmuted=$sessionInputAudioUnmuted}" + sessionOutputAudioFixturePlaying != null -> + "ForkServerEvent{sessionOutputAudioFixturePlaying=$sessionOutputAudioFixturePlaying}" + sessionOutputAudioFixturePlayed != null -> + "ForkServerEvent{sessionOutputAudioFixturePlayed=$sessionOutputAudioFixturePlayed}" sessionInstructionsAppended != null -> "ForkServerEvent{sessionInstructionsAppended=$sessionInstructionsAppended}" sessionThinkingAppended != null -> @@ -837,6 +911,27 @@ private constructor( fun ofSessionInputAudioUnmuted(sessionInputAudioUnmuted: InputAudioUnmutedEvent) = ForkServerEvent(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 + ) = ForkServerEvent(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 + ) = ForkServerEvent(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 @@ -1016,6 +1111,25 @@ private constructor( 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 @@ -1190,6 +1304,18 @@ private constructor( ForkServerEvent(sessionInputAudioUnmuted = it, _json = json) } ?: ForkServerEvent(_json = json) } + "session.output_audio.fixture.playing" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { + ForkServerEvent(sessionOutputAudioFixturePlaying = it, _json = json) + } ?: ForkServerEvent(_json = json) + } + "session.output_audio.fixture.played" -> { + return tryDeserialize(node, jacksonTypeRef()) + ?.let { + ForkServerEvent(sessionOutputAudioFixturePlayed = it, _json = json) + } ?: ForkServerEvent(_json = json) + } "session.instructions.appended" -> { return tryDeserialize(node, jacksonTypeRef())?.let { ForkServerEvent(sessionInstructionsAppended = it, _json = json) @@ -1300,6 +1426,10 @@ private constructor( 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 -> diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt index a6d8a3132..b6273f9de 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt @@ -72,6 +72,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -218,6 +219,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -324,6 +326,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).contains(sessionInputAudioAppend) assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -363,6 +366,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).contains(sessionInputAudioMute) assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -400,6 +404,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).contains(sessionInputAudioUnmute) + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -425,6 +430,48 @@ internal class ClientEventTest { assertThat(roundtrippedClientEvent).isEqualTo(clientEvent) } + @Test + fun ofSessionOutputAudioFixturePlay() { + val sessionOutputAudioFixturePlay = + OutputAudioFixturePlayEvent.builder().audio("AAAAAA==").eventId("evt_play_001").build() + + val clientEvent = ClientEvent.ofSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) + + assertThat(clientEvent.sessionStart()).isEmpty + assertThat(clientEvent.sessionUpdate()).isEmpty + assertThat(clientEvent.sessionInputAudioAppend()).isEmpty + assertThat(clientEvent.sessionInputAudioMute()).isEmpty + assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()) + .contains(sessionOutputAudioFixturePlay) + assertThat(clientEvent.sessionInstructionsAppend()).isEmpty + assertThat(clientEvent.sessionThinkingAppend()).isEmpty + assertThat(clientEvent.sessionCommentaryAppend()).isEmpty + assertThat(clientEvent.responseItemCreate()).isEmpty + assertThat(clientEvent.responseCreate()).isEmpty + assertThat(clientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayRoundtrip() { + val jsonMapper = jsonMapper() + val clientEvent = + ClientEvent.ofSessionOutputAudioFixturePlay( + OutputAudioFixturePlayEvent.builder() + .audio("AAAAAA==") + .eventId("evt_play_001") + .build() + ) + + val roundtrippedClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(clientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedClientEvent).isEqualTo(clientEvent) + } + @Test fun ofSessionInstructionsAppend() { val sessionInstructionsAppend = @@ -441,6 +488,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).contains(sessionInstructionsAppend) assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -486,6 +534,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).contains(sessionThinkingAppend) assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -531,6 +580,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).contains(sessionCommentaryAppend) @@ -584,6 +634,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -630,6 +681,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty @@ -666,6 +718,7 @@ internal class ClientEventTest { assertThat(clientEvent.sessionInputAudioAppend()).isEmpty assertThat(clientEvent.sessionInputAudioMute()).isEmpty assertThat(clientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(clientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(clientEvent.sessionInstructionsAppend()).isEmpty assertThat(clientEvent.sessionThinkingAppend()).isEmpty assertThat(clientEvent.sessionCommentaryAppend()).isEmpty diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/LiveVisitorCompatibilityTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/LiveVisitorCompatibilityTest.kt new file mode 100644 index 000000000..e201e3f37 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/LiveVisitorCompatibilityTest.kt @@ -0,0 +1,86 @@ +package com.openai.models.live + +import com.openai.core.JsonValue +import com.openai.models.live.forks.ForkClientEvent +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class LiveVisitorCompatibilityTest { + @Test + fun visitorWrittenBeforeFixturePlaybackHandlesFixtureAsUnknown() { + val visitor = + object : ClientEvent.Visitor { + override fun visitSessionStart(sessionStart: SessionStartEvent) = null + + override fun visitSessionUpdate(sessionUpdate: SessionUpdateEvent) = null + + override fun visitSessionInputAudioAppend( + sessionInputAudioAppend: InputAudioAppendEvent + ) = null + + override fun visitSessionInputAudioMute( + sessionInputAudioMute: InputAudioMuteEvent + ) = null + + override fun visitSessionInputAudioUnmute( + sessionInputAudioUnmute: InputAudioUnmuteEvent + ) = null + + override fun visitSessionInstructionsAppend( + sessionInstructionsAppend: InstructionsAppendEvent + ) = null + + override fun visitSessionThinkingAppend( + sessionThinkingAppend: ThinkingAppendEvent + ) = null + + override fun visitSessionCommentaryAppend( + sessionCommentaryAppend: CommentaryAppendEvent + ) = null + + override fun visitResponseItemCreate(responseItemCreate: ResponseItemCreateEvent) = + null + + override fun visitResponseCreate(responseCreate: ResponseCreateEvent) = null + + override fun visitSessionClose(sessionClose: SessionCloseEvent) = null + + override fun unknown(json: JsonValue?) = json + } + val fixture = OutputAudioFixturePlayEvent.builder().audio("AAABAA==").build() + + assertThat(ClientEvent.ofSessionOutputAudioFixturePlay(fixture).accept(visitor)) + .isEqualTo(JsonValue.from(fixture)) + } + + @Test + fun fixtureVisitorMethodsAreJvmDefaults() { + for ((visitor, method, event) in + listOf( + Triple( + ClientEvent.Visitor::class.java, + "visitSessionOutputAudioFixturePlay", + OutputAudioFixturePlayEvent::class.java, + ), + Triple( + ForkClientEvent.Visitor::class.java, + "visitSessionOutputAudioFixturePlay", + OutputAudioFixturePlayEvent::class.java, + ), + Triple( + ServerEvent.Visitor::class.java, + "visitSessionOutputAudioFixturePlaying", + OutputAudioFixturePlayingEvent::class.java, + ), + Triple( + ServerEvent.Visitor::class.java, + "visitSessionOutputAudioFixturePlayed", + OutputAudioFixturePlayedEvent::class.java, + ), + )) { + assertThat(visitor.getMethod(method, event).isDefault) + .describedAs("%s.%s is a JVM default method", visitor.name, method) + .isTrue() + } + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayEventTest.kt new file mode 100644 index 000000000..708ac5efc --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayEventTest.kt @@ -0,0 +1,35 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class OutputAudioFixturePlayEventTest { + + @Test + fun create() { + val outputAudioFixturePlayEvent = + OutputAudioFixturePlayEvent.builder().audio("AAAAAA==").eventId("evt_play_001").build() + + assertThat(outputAudioFixturePlayEvent.audio()).isEqualTo("AAAAAA==") + assertThat(outputAudioFixturePlayEvent.eventId()).contains("evt_play_001") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val outputAudioFixturePlayEvent = + OutputAudioFixturePlayEvent.builder().audio("AAAAAA==").eventId("evt_play_001").build() + + val roundtrippedOutputAudioFixturePlayEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(outputAudioFixturePlayEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedOutputAudioFixturePlayEvent).isEqualTo(outputAudioFixturePlayEvent) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayedEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayedEventTest.kt new file mode 100644 index 000000000..748add137 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayedEventTest.kt @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class OutputAudioFixturePlayedEventTest { + + @Test + fun create() { + val outputAudioFixturePlayedEvent = + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + + assertThat(outputAudioFixturePlayedEvent.eventId()).isEqualTo("evt_played_001") + assertThat(outputAudioFixturePlayedEvent.clientEventId()).contains("evt_play_001") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val outputAudioFixturePlayedEvent = + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + + val roundtrippedOutputAudioFixturePlayedEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(outputAudioFixturePlayedEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedOutputAudioFixturePlayedEvent) + .isEqualTo(outputAudioFixturePlayedEvent) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayingEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayingEventTest.kt new file mode 100644 index 000000000..6b8232140 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/OutputAudioFixturePlayingEventTest.kt @@ -0,0 +1,42 @@ +// File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. + +package com.openai.models.live + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class OutputAudioFixturePlayingEventTest { + + @Test + fun create() { + val outputAudioFixturePlayingEvent = + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + + assertThat(outputAudioFixturePlayingEvent.eventId()).isEqualTo("evt_playing_001") + assertThat(outputAudioFixturePlayingEvent.clientEventId()).contains("evt_play_001") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val outputAudioFixturePlayingEvent = + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + + val roundtrippedOutputAudioFixturePlayingEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(outputAudioFixturePlayingEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedOutputAudioFixturePlayingEvent) + .isEqualTo(outputAudioFixturePlayingEvent) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt index 1ba9c5209..281a3dfa5 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt @@ -61,6 +61,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -208,6 +210,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).contains(sessionUpdated) assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -322,6 +326,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).contains(sessionInputAudioMuted) assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -376,6 +382,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).contains(sessionInputAudioUnmuted) + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -416,6 +424,122 @@ internal class ServerEventTest { assertThat(roundtrippedServerEvent).isEqualTo(serverEvent) } + @Test + fun ofSessionOutputAudioFixturePlaying() { + val sessionOutputAudioFixturePlaying = + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + + val serverEvent = + ServerEvent.ofSessionOutputAudioFixturePlaying(sessionOutputAudioFixturePlaying) + + assertThat(serverEvent.sessionStarted()).isEmpty + assertThat(serverEvent.sessionUpdated()).isEmpty + assertThat(serverEvent.sessionInputAudioMuted()).isEmpty + assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()) + .contains(sessionOutputAudioFixturePlaying) + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(serverEvent.sessionInstructionsAppended()).isEmpty + assertThat(serverEvent.sessionThinkingAppended()).isEmpty + assertThat(serverEvent.sessionCommentaryAppended()).isEmpty + assertThat(serverEvent.sessionInputAudioAppend()).isEmpty + assertThat(serverEvent.sessionOutputAudioDelta()).isEmpty + assertThat(serverEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(serverEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(serverEvent.sessionDelegationCreated()).isEmpty + assertThat(serverEvent.responseEvent()).isEmpty + assertThat(serverEvent.sessionUsageUpdated()).isEmpty + assertThat(serverEvent.sessionClosed()).isEmpty + assertThat(serverEvent.errorEvent()).isEmpty + assertThat(serverEvent.infoEvent()).isEmpty + assertThat(serverEvent.transportDtmfReceived()).isEmpty + assertThat(serverEvent.transportDtmfSend()).isEmpty + assertThat(serverEvent.transportRinging()).isEmpty + assertThat(serverEvent.transportAnswered()).isEmpty + assertThat(serverEvent.transportFailed()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayingRoundtrip() { + val jsonMapper = jsonMapper() + val serverEvent = + ServerEvent.ofSessionOutputAudioFixturePlaying( + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(serverEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedServerEvent).isEqualTo(serverEvent) + } + + @Test + fun ofSessionOutputAudioFixturePlayed() { + val sessionOutputAudioFixturePlayed = + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + + val serverEvent = + ServerEvent.ofSessionOutputAudioFixturePlayed(sessionOutputAudioFixturePlayed) + + assertThat(serverEvent.sessionStarted()).isEmpty + assertThat(serverEvent.sessionUpdated()).isEmpty + assertThat(serverEvent.sessionInputAudioMuted()).isEmpty + assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()) + .contains(sessionOutputAudioFixturePlayed) + assertThat(serverEvent.sessionInstructionsAppended()).isEmpty + assertThat(serverEvent.sessionThinkingAppended()).isEmpty + assertThat(serverEvent.sessionCommentaryAppended()).isEmpty + assertThat(serverEvent.sessionInputAudioAppend()).isEmpty + assertThat(serverEvent.sessionOutputAudioDelta()).isEmpty + assertThat(serverEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(serverEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(serverEvent.sessionDelegationCreated()).isEmpty + assertThat(serverEvent.responseEvent()).isEmpty + assertThat(serverEvent.sessionUsageUpdated()).isEmpty + assertThat(serverEvent.sessionClosed()).isEmpty + assertThat(serverEvent.errorEvent()).isEmpty + assertThat(serverEvent.infoEvent()).isEmpty + assertThat(serverEvent.transportDtmfReceived()).isEmpty + assertThat(serverEvent.transportDtmfSend()).isEmpty + assertThat(serverEvent.transportRinging()).isEmpty + assertThat(serverEvent.transportAnswered()).isEmpty + assertThat(serverEvent.transportFailed()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayedRoundtrip() { + val jsonMapper = jsonMapper() + val serverEvent = + ServerEvent.ofSessionOutputAudioFixturePlayed( + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(serverEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedServerEvent).isEqualTo(serverEvent) + } + @Test fun ofSessionInstructionsAppended() { val sessionInstructionsAppended = @@ -432,6 +556,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).contains(sessionInstructionsAppended) assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -490,6 +616,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).contains(sessionThinkingAppended) assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -548,6 +676,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).contains(sessionCommentaryAppended) @@ -601,6 +731,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -653,6 +785,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -711,6 +845,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -771,6 +907,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -837,6 +975,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -914,6 +1054,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -990,6 +1132,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1083,6 +1227,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1181,6 +1327,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1248,6 +1396,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1303,6 +1453,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1355,6 +1507,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1410,6 +1564,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1464,6 +1620,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty @@ -1525,6 +1683,8 @@ internal class ServerEventTest { assertThat(serverEvent.sessionUpdated()).isEmpty assertThat(serverEvent.sessionInputAudioMuted()).isEmpty assertThat(serverEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(serverEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(serverEvent.sessionInstructionsAppended()).isEmpty assertThat(serverEvent.sessionThinkingAppended()).isEmpty assertThat(serverEvent.sessionCommentaryAppended()).isEmpty diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkClientEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkClientEventTest.kt index 4b63bb521..53e57e86a 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkClientEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkClientEventTest.kt @@ -17,6 +17,7 @@ import com.openai.models.live.InputAudioAppendEvent 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 @@ -123,6 +124,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -304,6 +306,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -410,6 +413,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).contains(sessionInputAudioAppend) assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -449,6 +453,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).contains(sessionInputAudioMute) assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -486,6 +491,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).contains(sessionInputAudioUnmute) + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -511,6 +517,49 @@ internal class ForkClientEventTest { assertThat(roundtrippedForkClientEvent).isEqualTo(forkClientEvent) } + @Test + fun ofSessionOutputAudioFixturePlay() { + val sessionOutputAudioFixturePlay = + OutputAudioFixturePlayEvent.builder().audio("AAAAAA==").eventId("evt_play_001").build() + + val forkClientEvent = + ForkClientEvent.ofSessionOutputAudioFixturePlay(sessionOutputAudioFixturePlay) + + assertThat(forkClientEvent.sessionStart()).isEmpty + assertThat(forkClientEvent.sessionUpdate()).isEmpty + assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty + assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty + assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()) + .contains(sessionOutputAudioFixturePlay) + assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty + assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty + assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty + assertThat(forkClientEvent.responseItemCreate()).isEmpty + assertThat(forkClientEvent.responseCreate()).isEmpty + assertThat(forkClientEvent.sessionClose()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayRoundtrip() { + val jsonMapper = jsonMapper() + val forkClientEvent = + ForkClientEvent.ofSessionOutputAudioFixturePlay( + OutputAudioFixturePlayEvent.builder() + .audio("AAAAAA==") + .eventId("evt_play_001") + .build() + ) + + val roundtrippedForkClientEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(forkClientEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedForkClientEvent).isEqualTo(forkClientEvent) + } + @Test fun ofSessionInstructionsAppend() { val sessionInstructionsAppend = @@ -527,6 +576,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).contains(sessionInstructionsAppend) assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -572,6 +622,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).contains(sessionThinkingAppend) assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -617,6 +668,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).contains(sessionCommentaryAppend) @@ -670,6 +722,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -716,6 +769,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty @@ -752,6 +806,7 @@ internal class ForkClientEventTest { assertThat(forkClientEvent.sessionInputAudioAppend()).isEmpty assertThat(forkClientEvent.sessionInputAudioMute()).isEmpty assertThat(forkClientEvent.sessionInputAudioUnmute()).isEmpty + assertThat(forkClientEvent.sessionOutputAudioFixturePlay()).isEmpty assertThat(forkClientEvent.sessionInstructionsAppend()).isEmpty assertThat(forkClientEvent.sessionThinkingAppend()).isEmpty assertThat(forkClientEvent.sessionCommentaryAppend()).isEmpty diff --git a/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkServerEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkServerEventTest.kt index ecff5a76a..2328d364b 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkServerEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/live/forks/ForkServerEventTest.kt @@ -21,6 +21,8 @@ import com.openai.models.live.InputAudioUnmutedEvent import com.openai.models.live.InputTranscriptDeltaEvent import com.openai.models.live.InstructionsAppendedEvent import com.openai.models.live.OutputAudioDeltaEvent +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 @@ -86,6 +88,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -233,6 +237,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).contains(sessionUpdated) assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -347,6 +353,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).contains(sessionInputAudioMuted) assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -401,6 +409,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).contains(sessionInputAudioUnmuted) + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -441,6 +451,122 @@ internal class ForkServerEventTest { assertThat(roundtrippedForkServerEvent).isEqualTo(forkServerEvent) } + @Test + fun ofSessionOutputAudioFixturePlaying() { + val sessionOutputAudioFixturePlaying = + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + + val forkServerEvent = + ForkServerEvent.ofSessionOutputAudioFixturePlaying(sessionOutputAudioFixturePlaying) + + assertThat(forkServerEvent.sessionStarted()).isEmpty + assertThat(forkServerEvent.sessionUpdated()).isEmpty + assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()) + .contains(sessionOutputAudioFixturePlaying) + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty + assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty + assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(forkServerEvent.sessionInputAudioAppend()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioDelta()).isEmpty + assertThat(forkServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(forkServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(forkServerEvent.sessionDelegationCreated()).isEmpty + assertThat(forkServerEvent.responseEvent()).isEmpty + assertThat(forkServerEvent.sessionUsageUpdated()).isEmpty + assertThat(forkServerEvent.sessionClosed()).isEmpty + assertThat(forkServerEvent.errorEvent()).isEmpty + assertThat(forkServerEvent.infoEvent()).isEmpty + assertThat(forkServerEvent.transportDtmfReceived()).isEmpty + assertThat(forkServerEvent.transportDtmfSend()).isEmpty + assertThat(forkServerEvent.transportRinging()).isEmpty + assertThat(forkServerEvent.transportAnswered()).isEmpty + assertThat(forkServerEvent.transportFailed()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayingRoundtrip() { + val jsonMapper = jsonMapper() + val forkServerEvent = + ForkServerEvent.ofSessionOutputAudioFixturePlaying( + OutputAudioFixturePlayingEvent.builder() + .eventId("evt_playing_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedForkServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(forkServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedForkServerEvent).isEqualTo(forkServerEvent) + } + + @Test + fun ofSessionOutputAudioFixturePlayed() { + val sessionOutputAudioFixturePlayed = + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + + val forkServerEvent = + ForkServerEvent.ofSessionOutputAudioFixturePlayed(sessionOutputAudioFixturePlayed) + + assertThat(forkServerEvent.sessionStarted()).isEmpty + assertThat(forkServerEvent.sessionUpdated()).isEmpty + assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty + assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()) + .contains(sessionOutputAudioFixturePlayed) + assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty + assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty + assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty + assertThat(forkServerEvent.sessionInputAudioAppend()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioDelta()).isEmpty + assertThat(forkServerEvent.sessionInputTranscriptDelta()).isEmpty + assertThat(forkServerEvent.sessionOutputTranscriptDelta()).isEmpty + assertThat(forkServerEvent.sessionDelegationCreated()).isEmpty + assertThat(forkServerEvent.responseEvent()).isEmpty + assertThat(forkServerEvent.sessionUsageUpdated()).isEmpty + assertThat(forkServerEvent.sessionClosed()).isEmpty + assertThat(forkServerEvent.errorEvent()).isEmpty + assertThat(forkServerEvent.infoEvent()).isEmpty + assertThat(forkServerEvent.transportDtmfReceived()).isEmpty + assertThat(forkServerEvent.transportDtmfSend()).isEmpty + assertThat(forkServerEvent.transportRinging()).isEmpty + assertThat(forkServerEvent.transportAnswered()).isEmpty + assertThat(forkServerEvent.transportFailed()).isEmpty + } + + @Test + fun ofSessionOutputAudioFixturePlayedRoundtrip() { + val jsonMapper = jsonMapper() + val forkServerEvent = + ForkServerEvent.ofSessionOutputAudioFixturePlayed( + OutputAudioFixturePlayedEvent.builder() + .eventId("evt_played_001") + .clientEventId("evt_play_001") + .build() + ) + + val roundtrippedForkServerEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(forkServerEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedForkServerEvent).isEqualTo(forkServerEvent) + } + @Test fun ofSessionInstructionsAppended() { val sessionInstructionsAppended = @@ -458,6 +584,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()) .contains(sessionInstructionsAppended) assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty @@ -517,6 +645,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).contains(sessionThinkingAppended) assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -575,6 +705,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).contains(sessionCommentaryAppended) @@ -628,6 +760,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -680,6 +814,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -739,6 +875,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -801,6 +939,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -867,6 +1007,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -944,6 +1086,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1020,6 +1164,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1113,6 +1259,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1211,6 +1359,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1278,6 +1428,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1333,6 +1485,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1388,6 +1542,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1443,6 +1599,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1497,6 +1655,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty @@ -1558,6 +1718,8 @@ internal class ForkServerEventTest { assertThat(forkServerEvent.sessionUpdated()).isEmpty assertThat(forkServerEvent.sessionInputAudioMuted()).isEmpty assertThat(forkServerEvent.sessionInputAudioUnmuted()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlaying()).isEmpty + assertThat(forkServerEvent.sessionOutputAudioFixturePlayed()).isEmpty assertThat(forkServerEvent.sessionInstructionsAppended()).isEmpty assertThat(forkServerEvent.sessionThinkingAppended()).isEmpty assertThat(forkServerEvent.sessionCommentaryAppended()).isEmpty