diff --git a/.castiron.stats.yml b/.castiron.stats.yml index f3cdfea8d..9770ecf28 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: fd87a332-8b41-4969-82ea-bee78e04c9b0 -openapi_spec_hash: f0ac7c2da11e51bf51fe8f65e5b313b9 -openapi_transformed_spec_hash: 90042e27fae59af49799aee80b3882b0 -config_hash: 3336a08a5a6cf26a723db96a2602e503 -codegen_sha: c76b241333d2b0ecd43eb9f8a4bc308fea1defa1 -codegen_hash: 12b19e5d66cb42739f0d86618cd872d65c9067cfcf395cef3c9fbf2bfd359de1 -public_codegen_sha: 4dd493cf89ad33dd52f93d8e2485f8ed7a4fdede +generation_id: c67fe055-5fef-4490-a93e-212d8568459a +openapi_spec_hash: 9585fb5cd9d39a73de012b8de53b088a +openapi_transformed_spec_hash: c497de66b4332c26986df2cb087da34a +config_hash: 075dc8d5c682945dd2c56e77607736a1 +codegen_sha: c9f081c5aff42abd96fb8a2bb6aa0cb3edb29ce0 +codegen_hash: 1752270cf5c4363ac5590798437d9abe9a17fc97188f305211630acb2dc1ce4d +public_codegen_sha: 9b09f5dcd2bec975a9539d4d08f4b87358de65e8 diff --git a/api_reference/openapi.transformed.yml b/api_reference/openapi.transformed.yml index 71612527b..9cd67be44 100644 --- a/api_reference/openapi.transformed.yml +++ b/api_reference/openapi.transformed.yml @@ -1691,11 +1691,11 @@ paths: - Audio summary: Create voice description: | - Creates a voice from a text prompt or from a consent recording and an audio sample. + Create a custom voice you can use for audio output (for example, in Text-to-Speech and the Realtime API). This requires an audio sample and a previously uploaded consent recording. - For prompt-based creation, send `type: "prompt"` with a `name` and `prompt` as JSON or multipart form data. For creation from an audio sample, send `type: "audio_sample"` with a `name`, `audio_sample`, and `consent` recording ID as multipart form data. The type defaults to `audio_sample` when omitted. + Send `name`, `audio_sample`, and the `consent` recording ID as multipart form data. The optional `type` defaults to `audio_sample`. - Returns the saved voice's metadata. Voices created from text prompts are supported only in Live, not in Realtime or the speech endpoint. The response does not include preview audio. + Returns the saved voice's metadata. See the [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) for requirements and best practices. Custom voices are limited to eligible customers. requestBody: required: true content: @@ -1710,7 +1710,7 @@ paths: schema: $ref: '#/components/schemas/VoiceResource' '400': - description: Invalid voice request, prompt, script hint, model, consent recording, or audio sample. + description: Invalid voice request, consent recording, or audio sample. content: application/json: schema: @@ -1735,13 +1735,9 @@ paths: curl https://api.openai.com/v1/audio/voices \ -X POST \ -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "type": "prompt", - "name": "Warm narrator", - "prompt": "A warm, calm narrator with a clear, measured delivery.", - "model": "auto" - }' + -F "name=My new voice" \ + -F "consent=cons_1234" \ + -F "audio_sample=@audio_sample.wav;type=audio/x-wav" /batches: post: summary: Create batch @@ -40805,8 +40801,7 @@ components: `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `nova`, `onyx`, `sage`, `shimmer`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. - Custom voices must be created from audio samples. Voices created from text - prompts are supported only in Live. + Custom voices must be created from audio samples. format: type: string enum: @@ -43646,7 +43641,7 @@ components: description: Control the voice of your generated audio with additional instructions. Does not work with `tts-1` or `tts-1-hd`. maxLength: 4096 voice: - description: 'The voice to use when generating the audio. Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the voices are available in the [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). Custom voices must be created from audio samples. Voices created from text prompts are supported only in Live.' + description: 'The voice to use when generating the audio. Supported built-in voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Previews of the voices are available in the [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). Custom voices must be created from audio samples.' title: Voice anyOf: - anyOf: @@ -44531,55 +44526,6 @@ components: - name - audio_sample - consent - CreateVoicePromptRequest: - type: object - title: Prompt - description: Creates a synthetic voice from a text description. Supports application/json or multipart/form-data. - additionalProperties: false - properties: - type: - type: string - enum: - - prompt - description: Set to `prompt` to create a voice from a text description. - name: - type: string - minLength: 1 - maxLength: 256 - description: The name of the new voice. - prompt: - type: string - minLength: 1 - maxLength: 2000 - description: A description of the desired voice. Must not contain only whitespace. - script_hint: - type: string - minLength: 1 - maxLength: 2000 - description: Optional text for the voice to speak during creation. If omitted, a script is generated from the prompt. Must not be blank after trimming whitespace; scripts that are too short are rejected. - model: - anyOf: - - type: string - - type: string - enum: - - auto - - 2026-10-01 - default: auto - description: The voice creation model to use. Defaults to `auto`. - required: - - type - - name - - prompt - CreateVoiceRequest: - discriminator: - propertyName: type - mapping: - audio_sample: '#/components/schemas/CreateVoiceFromConsentRequest' - prompt: '#/components/schemas/CreateVoicePromptRequest' - x-oai-default-discriminator-value: audio_sample - anyOf: - - $ref: '#/components/schemas/CreateVoiceFromConsentRequest' - - $ref: '#/components/schemas/CreateVoicePromptRequest' CustomToolCall: type: object title: Custom tool call @@ -54173,7 +54119,7 @@ components: `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be changed during the session once the model has responded with audio at least once. - Custom voices must be created from audio samples. Voices created from text prompts are supported only in Live. + Custom voices must be created from audio samples. output_audio_format: type: string enum: @@ -57514,7 +57460,7 @@ components: `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be changed during the session once the model has responded with audio at least once. - Custom voices must be created from audio samples. Voices created from text prompts are supported only in Live. + Custom voices must be created from audio samples. We recommend `marin` and `cedar` for best quality. tools: type: array @@ -60087,7 +60033,7 @@ components: `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be changed during the session once the model has responded with audio at least once. - Custom voices must be created from audio samples. Voices created from text prompts are supported only in Live. + Custom voices must be created from audio samples. input_audio_format: type: string description: | @@ -60367,7 +60313,7 @@ components: `marin`, and `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be changed during the session once the model has responded with audio at least once. - Custom voices must be created from audio samples. Voices created from text prompts are supported only in Live. + Custom voices must be created from audio samples. We recommend `marin` and `cedar` for best quality. speed: type: number @@ -70985,7 +70931,7 @@ components: VoiceResource: type: object title: Voice - description: A custom voice that can be used for audio output. Voices created from text prompts are supported only in Live. + description: A custom voice that can be used for audio output. additionalProperties: false properties: object: @@ -70998,11 +70944,10 @@ components: type: string description: The voice identifier, which can be referenced in API endpoints. type: + description: How the voice was created. type: string - description: How the voice was created. Voices created from text prompts are supported only in Live. enum: - audio_sample - - prompt name: type: string description: The name of the voice. @@ -104418,6 +104363,14 @@ components: - $ref: '#/components/schemas/BetaResponseCustomToolCallInputDoneEvent' discriminator: propertyName: type + CreateVoiceRequest: + discriminator: + propertyName: type + mapping: + audio_sample: '#/components/schemas/CreateVoiceFromConsentRequest' + x-oai-default-discriminator-value: audio_sample + anyOf: + - $ref: '#/components/schemas/CreateVoiceFromConsentRequest' OAuthErrorCode: anyOf: - enum: diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/speech/SpeechCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/speech/SpeechCreateParams.kt index a8dc27c60..9b5db1f74 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/speech/SpeechCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/speech/SpeechCreateParams.kt @@ -68,8 +68,7 @@ private constructor( * `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": * "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. * * @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). @@ -253,8 +252,7 @@ private constructor( * and `cedar`. You may also provide a custom voice object with an `id`, for example `{ * "id": "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. */ fun voice(voice: Voice) = apply { body.voice(voice) } @@ -550,8 +548,7 @@ private constructor( * and `cedar`. You may also provide a custom voice object with an `id`, for example `{ * "id": "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. * * @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). @@ -748,8 +745,7 @@ private constructor( * `id`, for example `{ "id": "voice_1234" }`. Previews of the voices are available in * the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. + * Custom voices must be created from audio samples. */ fun voice(voice: Voice) = voice(JsonField.of(voice)) @@ -975,8 +971,7 @@ private constructor( * `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": * "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. */ @JsonDeserialize(using = Voice.Deserializer::class) @JsonSerialize(using = Voice.Serializer::class) @@ -996,8 +991,7 @@ private constructor( * and `cedar`. You may also provide a custom voice object with an `id`, for example `{ * "id": "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. */ fun unionMember1(): Optional = Optional.ofNullable(unionMember1) @@ -1018,8 +1012,7 @@ private constructor( * and `cedar`. You may also provide a custom voice object with an `id`, for example `{ * "id": "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. */ fun asUnionMember1(): UnionMember1 = unionMember1.getOrThrow("unionMember1") @@ -1159,8 +1152,7 @@ private constructor( * `id`, for example `{ "id": "voice_1234" }`. Previews of the voices are available in * the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. + * Custom voices must be created from audio samples. */ @JvmStatic fun ofUnionMember1(unionMember1: UnionMember1) = Voice(unionMember1 = unionMember1) @@ -1181,8 +1173,7 @@ private constructor( * `id`, for example `{ "id": "voice_1234" }`. Previews of the voices are available in * the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. + * Custom voices must be created from audio samples. */ fun visitUnionMember1(unionMember1: UnionMember1): T @@ -1260,8 +1251,7 @@ private constructor( * and `cedar`. You may also provide a custom voice object with an `id`, for example `{ * "id": "voice_1234" }`. Previews of the voices are available in the * [Text to speech guide](https://developers.openai.com/api/docs/guides/text-to-speech#voice-options). - * Custom voices must be created from audio samples. Voices created from text prompts are - * supported only in Live. + * Custom voices must be created from audio samples. */ class UnionMember1 @JsonCreator private constructor(private val value: JsonField) : Enum { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/Voice.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/Voice.kt index 10d793633..f9b832436 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/Voice.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/Voice.kt @@ -17,10 +17,7 @@ import java.util.Collections import java.util.Objects import kotlin.jvm.optionals.getOrNull -/** - * A custom voice that can be used for audio output. Voices created from text prompts are supported - * only in Live. - */ +/** A custom voice that can be used for audio output. */ class Voice @JsonCreator(mode = JsonCreator.Mode.DISABLED) private constructor( @@ -79,7 +76,7 @@ private constructor( @JsonProperty("object") @ExcludeMissing fun _object_(): JsonValue = object_ /** - * How the voice was created. Voices created from text prompts are supported only in Live. + * How the voice was created. * * @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). @@ -209,9 +206,7 @@ private constructor( */ fun object_(object_: JsonValue) = apply { this.object_ = object_ } - /** - * How the voice was created. Voices created from text prompts are supported only in Live. - */ + /** How the voice was created. */ fun type(type: Type) = type(JsonField.of(type)) /** @@ -315,7 +310,7 @@ private constructor( object_.let { if (it == JsonValue.from("audio.voice")) 1 else 0 } + (type.asKnown().getOrNull()?.validity() ?: 0) - /** How the voice was created. Voices created from text prompts are supported only in Live. */ + /** How the voice was created. */ class Type @JsonCreator private constructor(private val value: JsonField) : Enum { /** @@ -332,15 +327,12 @@ private constructor( @JvmField val AUDIO_SAMPLE = of("audio_sample") - @JvmField val PROMPT = of("prompt") - @JvmStatic fun of(value: String) = Type(JsonField.of(value)) } /** An enum containing [Type]'s known values. */ enum class Known { - AUDIO_SAMPLE, - PROMPT, + AUDIO_SAMPLE } /** @@ -354,7 +346,6 @@ private constructor( */ enum class Value { AUDIO_SAMPLE, - PROMPT, /** An enum member indicating that [Type] was instantiated with an unknown value. */ _UNKNOWN, } @@ -369,7 +360,6 @@ private constructor( fun value(): Value = when (this) { AUDIO_SAMPLE -> Value.AUDIO_SAMPLE - PROMPT -> Value.PROMPT else -> Value._UNKNOWN } @@ -385,7 +375,6 @@ private constructor( fun known(): Known = when (this) { AUDIO_SAMPLE -> Known.AUDIO_SAMPLE - PROMPT -> Known.PROMPT else -> throw OpenAIInvalidDataException("Unknown Type: $value") } diff --git a/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/VoiceCreateParams.kt b/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/VoiceCreateParams.kt index 8bb9afad5..41e756d18 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/VoiceCreateParams.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/audio/voices/VoiceCreateParams.kt @@ -28,15 +28,15 @@ import java.util.Objects import java.util.Optional /** - * Creates a voice from a text prompt or from a consent recording and an audio sample. + * Create a custom voice you can use for audio output (for example, in Text-to-Speech and the + * Realtime API). This requires an audio sample and a previously uploaded consent recording. * - * For prompt-based creation, send `type: "prompt"` with a `name` and `prompt` as JSON or multipart - * form data. For creation from an audio sample, send `type: "audio_sample"` with a `name`, - * `audio_sample`, and `consent` recording ID as multipart form data. The type defaults to - * `audio_sample` when omitted. + * Send `name`, `audio_sample`, and the `consent` recording ID as multipart form data. The optional + * `type` defaults to `audio_sample`. * - * Returns the saved voice's metadata. Voices created from text prompts are supported only in Live, - * not in Realtime or the speech endpoint. The response does not include preview audio. + * Returns the saved voice's metadata. See the + * [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) + * for requirements and best practices. Custom voices are limited to eligible customers. */ class VoiceCreateParams private constructor( @@ -94,9 +94,6 @@ private constructor( /** Alias for calling [body] with `Body.ofAudioSample(audioSample)`. */ fun body(audioSample: Body.AudioSample) = body(Body.ofAudioSample(audioSample)) - /** Alias for calling [body] with `Body.ofPrompt(prompt)`. */ - fun body(prompt: Body.Prompt) = body(Body.ofPrompt(prompt)) - fun additionalHeaders(additionalHeaders: Headers) = apply { this.additionalHeaders.clear() putAllAdditionalHeaders(additionalHeaders) @@ -227,17 +224,6 @@ private constructor( this.body.asAudioSample()._additionalProperties().mapValues { (_, value) -> MultipartField.of(value) } - this.body.isPrompt() -> - mapOf( - "name" to this.body.asPrompt()._name(), - "prompt" to this.body.asPrompt()._prompt(), - "type" to MultipartField.of(this.body.asPrompt()._type()), - "model" to this.body.asPrompt()._model(), - "script_hint" to this.body.asPrompt()._scriptHint(), - ) + - this.body.asPrompt()._additionalProperties().mapValues { (_, value) -> - MultipartField.of(value) - } else -> this.body ._json() @@ -258,7 +244,6 @@ private constructor( class Body private constructor( private val audioSample: AudioSample? = null, - private val prompt: Prompt? = null, private val _json: JsonValue? = null, ) { @@ -268,28 +253,14 @@ private constructor( */ fun audioSample(): Optional = Optional.ofNullable(audioSample) - /** - * Creates a synthetic voice from a text description. Supports application/json or - * multipart/form-data. - */ - fun prompt(): Optional = Optional.ofNullable(prompt) - fun isAudioSample(): Boolean = audioSample != null - fun isPrompt(): Boolean = prompt != null - /** * Creates a voice from a consent recording and an audio sample. Requires * multipart/form-data. */ fun asAudioSample(): AudioSample = audioSample.getOrThrow("audioSample") - /** - * Creates a synthetic voice from a text description. Supports application/json or - * multipart/form-data. - */ - fun asPrompt(): Prompt = prompt.getOrThrow("prompt") - fun _json(): Optional = Optional.ofNullable(_json) /** @@ -324,7 +295,6 @@ private constructor( fun accept(visitor: Visitor): T = when { audioSample != null -> visitor.visitAudioSample(audioSample) - prompt != null -> visitor.visitPrompt(prompt) else -> visitor.unknown(_json) } @@ -349,10 +319,6 @@ private constructor( override fun visitAudioSample(audioSample: AudioSample) { audioSample.validate() } - - override fun visitPrompt(prompt: Prompt) { - prompt.validate() - } } ) validated = true @@ -371,15 +337,14 @@ private constructor( return true } - return other is Body && audioSample == other.audioSample && prompt == other.prompt + return other is Body && audioSample == other.audioSample } - override fun hashCode(): Int = Objects.hash(audioSample, prompt) + override fun hashCode(): Int = Objects.hash(audioSample) override fun toString(): String = when { audioSample != null -> "Body{audioSample=$audioSample}" - prompt != null -> "Body{prompt=$prompt}" _json != null -> "Body{_unknown=$_json}" else -> throw IllegalStateException("Invalid Body") } @@ -391,12 +356,6 @@ private constructor( * multipart/form-data. */ @JvmStatic fun ofAudioSample(audioSample: AudioSample) = Body(audioSample = audioSample) - - /** - * Creates a synthetic voice from a text description. Supports application/json or - * multipart/form-data. - */ - @JvmStatic fun ofPrompt(prompt: Prompt) = Body(prompt = prompt) } /** An interface that defines how to map each variant of [Body] to a value of type [T]. */ @@ -408,12 +367,6 @@ private constructor( */ fun visitAudioSample(audioSample: AudioSample): T - /** - * Creates a synthetic voice from a text description. Supports application/json or - * multipart/form-data. - */ - fun visitPrompt(prompt: Prompt): T - /** * Maps an unknown variant of [Body] to a value of type [T]. * @@ -437,7 +390,6 @@ private constructor( ) { when { value.audioSample != null -> generator.writeObject(value.audioSample) - value.prompt != null -> generator.writeObject(value.prompt) value._json != null -> generator.writeObject(value._json) else -> throw IllegalStateException("Invalid Body") } @@ -867,475 +819,6 @@ private constructor( override fun toString() = "AudioSample{audioSample=$audioSample, consent=$consent, name=$name, type=$type, additionalProperties=$additionalProperties}" } - - /** - * Creates a synthetic voice from a text description. Supports application/json or - * multipart/form-data. - */ - class Prompt - private constructor( - private val name: MultipartField, - private val prompt: MultipartField, - private val type: JsonValue, - private val model: MultipartField, - private val scriptHint: MultipartField, - private val additionalProperties: MutableMap, - ) { - - /** - * The name of the new voice. - * - * @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 name(): String = name.value.getRequired("name") - - /** - * A description of the desired voice. Must not contain only whitespace. - * - * @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 prompt(): String = prompt.value.getRequired("prompt") - - /** - * Set to `prompt` to create a voice from a text description. - * - * Expected to always return the following: - * ```java - * JsonValue.from("prompt") - * ``` - * - * 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 voice creation model to use. Defaults to `auto`. - * - * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if - * the server responded with an unexpected value). - */ - fun model(): Optional = model.value.getOptional("model") - - /** - * Optional text for the voice to speak during creation. If omitted, a script is - * generated from the prompt. Must not be blank after trimming whitespace; scripts that - * are too short are rejected. - * - * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if - * the server responded with an unexpected value). - */ - fun scriptHint(): Optional = scriptHint.value.getOptional("script_hint") - - /** - * Returns the raw multipart value of [name]. - * - * Unlike [name], this method doesn't throw if the multipart field has an unexpected - * type. - */ - @JsonProperty("name") @ExcludeMissing fun _name(): MultipartField = name - - /** - * Returns the raw multipart value of [prompt]. - * - * Unlike [prompt], this method doesn't throw if the multipart field has an unexpected - * type. - */ - @JsonProperty("prompt") @ExcludeMissing fun _prompt(): MultipartField = prompt - - /** - * Returns the raw multipart value of [model]. - * - * Unlike [model], this method doesn't throw if the multipart field has an unexpected - * type. - */ - @JsonProperty("model") @ExcludeMissing fun _model(): MultipartField = model - - /** - * Returns the raw multipart value of [scriptHint]. - * - * Unlike [scriptHint], this method doesn't throw if the multipart field has an - * unexpected type. - */ - @JsonProperty("script_hint") - @ExcludeMissing - fun _scriptHint(): MultipartField = scriptHint - - @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 [Prompt]. - * - * The following fields are required: - * ```java - * .name() - * .prompt() - * ``` - */ - @JvmStatic fun builder() = Builder() - } - - /** A builder for [Prompt]. */ - class Builder internal constructor() { - - private var name: MultipartField? = null - private var prompt: MultipartField? = null - private var type: JsonValue = JsonValue.from("prompt") - private var model: MultipartField = MultipartField.of(JsonMissing.of()) - private var scriptHint: MultipartField = MultipartField.of(JsonMissing.of()) - private var additionalProperties: MutableMap = mutableMapOf() - - @JvmSynthetic - internal fun from(prompt: Prompt) = apply { - name = prompt.name - this.prompt = prompt.prompt - type = prompt.type - model = prompt.model - scriptHint = prompt.scriptHint - additionalProperties = prompt.additionalProperties.toMutableMap() - } - - /** The name of the new voice. */ - fun name(name: String) = name(MultipartField.of(name)) - - /** - * Sets [Builder.name] to an arbitrary multipart value. - * - * You should usually call [Builder.name] with a well-typed [String] value instead. - * This method is primarily for setting the field to an undocumented or not yet - * supported value. - */ - fun name(name: MultipartField) = apply { this.name = name } - - /** A description of the desired voice. Must not contain only whitespace. */ - fun prompt(prompt: String) = prompt(MultipartField.of(prompt)) - - /** - * Sets [Builder.prompt] to an arbitrary multipart value. - * - * You should usually call [Builder.prompt] with a well-typed [String] value - * instead. This method is primarily for setting the field to an undocumented or not - * yet supported value. - */ - fun prompt(prompt: MultipartField) = apply { this.prompt = prompt } - - /** - * 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("prompt") - * ``` - * - * 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 voice creation model to use. Defaults to `auto`. */ - fun model(model: Model) = model(MultipartField.of(model)) - - /** - * Sets [Builder.model] to an arbitrary multipart value. - * - * You should usually call [Builder.model] with a well-typed [Model] value instead. - * This method is primarily for setting the field to an undocumented or not yet - * supported value. - */ - fun model(model: MultipartField) = apply { this.model = model } - - /** - * Sets [model] to an arbitrary [String]. - * - * You should usually call [model] with a well-typed [Model] constant instead. This - * method is primarily for setting the field to an undocumented or not yet supported - * value. - */ - fun model(value: String) = model(Model.of(value)) - - /** - * Optional text for the voice to speak during creation. If omitted, a script is - * generated from the prompt. Must not be blank after trimming whitespace; scripts - * that are too short are rejected. - */ - fun scriptHint(scriptHint: String) = scriptHint(MultipartField.of(scriptHint)) - - /** - * Sets [Builder.scriptHint] to an arbitrary multipart value. - * - * You should usually call [Builder.scriptHint] with a well-typed [String] value - * instead. This method is primarily for setting the field to an undocumented or not - * yet supported value. - */ - fun scriptHint(scriptHint: MultipartField) = apply { - this.scriptHint = scriptHint - } - - 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 [Prompt]. - * - * Further updates to this [Builder] will not mutate the returned instance. - * - * The following fields are required: - * ```java - * .name() - * .prompt() - * ``` - * - * @throws IllegalStateException if any required field is unset. - */ - fun build(): Prompt = - Prompt( - checkRequired("name", name), - checkRequired("prompt", prompt), - type, - model, - scriptHint, - 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(): Prompt = apply { - if (validated) { - return@apply - } - - name() - prompt() - _type().let { - if (it != JsonValue.from("prompt")) { - throw OpenAIInvalidDataException("'type' is invalid, received $it") - } - } - model() - scriptHint() - validated = true - } - - fun isValid(): Boolean = - try { - validate() - true - } catch (e: OpenAIInvalidDataException) { - false - } - - /** The voice creation model to use. Defaults to `auto`. */ - class Model @JsonCreator private constructor(private val value: JsonField) : - Enum { - - /** - * Returns this class instance's raw value. - * - * This is usually only useful if this instance was deserialized from data that - * doesn't match any known member, and you want to know that value. For example, if - * the SDK is on an older version than the API, then the API may respond with new - * members that the SDK is unaware of. - */ - @com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField = value - - companion object { - - @JvmField val AUTO = of("auto") - - @JvmField val _2026_10_01 = of("2026-10-01") - - @JvmStatic fun of(value: String) = Model(JsonField.of(value)) - } - - /** An enum containing [Model]'s known values. */ - enum class Known { - AUTO, - _2026_10_01, - } - - /** - * An enum containing [Model]'s known values, as well as an [_UNKNOWN] member. - * - * An instance of [Model] can contain an unknown value in a couple of cases: - * - It was deserialized from data that doesn't match any known member. For example, - * if the SDK is on an older version than the API, then the API may respond with - * new members that the SDK is unaware of. - * - It was constructed with an arbitrary value using the [of] method. - */ - enum class Value { - AUTO, - _2026_10_01, - /** - * An enum member indicating that [Model] was instantiated with an unknown - * value. - */ - _UNKNOWN, - } - - /** - * Returns an enum member corresponding to this class instance's value, or - * [Value._UNKNOWN] if the class was instantiated with an unknown value. - * - * Use the [known] method instead if you're certain the value is always known or if - * you want to throw for the unknown case. - */ - fun value(): Value = - when (this) { - AUTO -> Value.AUTO - _2026_10_01 -> Value._2026_10_01 - else -> Value._UNKNOWN - } - - /** - * Returns an enum member corresponding to this class instance's value. - * - * Use the [value] method instead if you're uncertain the value is always known and - * don't want to throw for the unknown case. - * - * @throws OpenAIInvalidDataException if this class instance's value is a not a - * known member. - */ - fun known(): Known = - when (this) { - AUTO -> Known.AUTO - _2026_10_01 -> Known._2026_10_01 - else -> throw OpenAIInvalidDataException("Unknown Model: $value") - } - - /** - * Returns this class instance's primitive wire representation. - * - * This differs from the [toString] method because that method is primarily for - * debugging and generally doesn't throw. - * - * @throws OpenAIInvalidDataException if this class instance's value does not have - * the expected primitive type. - */ - fun asString(): String = - _value().asString().orElseThrow { - OpenAIInvalidDataException("Value is not a String") - } - - private var validated: Boolean = false - - /** - * Validates that the types of all values in this object match their expected types - * recursively. - * - * This method is _not_ forwards compatible with new types from the API for existing - * fields. - * - * @throws OpenAIInvalidDataException if any value type in this object doesn't match - * its expected type. - */ - fun validate(): Model = apply { - if (validated) { - return@apply - } - - known() - 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 (value() == Value._UNKNOWN) 0 else 1 - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is Model && value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - - return other is Prompt && - name == other.name && - prompt == other.prompt && - type == other.type && - model == other.model && - scriptHint == other.scriptHint && - additionalProperties == other.additionalProperties - } - - private val hashCode: Int by lazy { - Objects.hash(name, prompt, type, model, scriptHint, additionalProperties) - } - - override fun hashCode(): Int = hashCode - - override fun toString() = - "Prompt{name=$name, prompt=$prompt, type=$type, model=$model, scriptHint=$scriptHint, additionalProperties=$additionalProperties}" - } } override fun equals(other: Any?): Boolean { diff --git a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionAudioParam.kt b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionAudioParam.kt index 3378f71c3..1cf97dbbd 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionAudioParam.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionAudioParam.kt @@ -59,8 +59,7 @@ private constructor( * The voice the model uses to respond. Supported built-in voices are `alloy`, `ash`, `ballad`, * `coral`, `echo`, `fable`, `nova`, `onyx`, `sage`, `shimmer`, `marin`, and `cedar`. You may * also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Custom - * voices must be created from audio samples. Voices created from text prompts are supported - * only in Live. + * voices must be created from audio samples. * * @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). @@ -139,8 +138,7 @@ private constructor( * The voice the model uses to respond. Supported built-in voices are `alloy`, `ash`, * `ballad`, `coral`, `echo`, `fable`, `nova`, `onyx`, `sage`, `shimmer`, `marin`, and * `cedar`. You may also provide a custom voice object with an `id`, for example `{ "id": - * "voice_1234" }`. Custom voices must be created from audio samples. Voices created from - * text prompts are supported only in Live. + * "voice_1234" }`. Custom voices must be created from audio samples. */ fun voice(voice: Voice) = voice(JsonField.of(voice)) @@ -404,8 +402,7 @@ private constructor( * The voice the model uses to respond. Supported built-in voices are `alloy`, `ash`, `ballad`, * `coral`, `echo`, `fable`, `nova`, `onyx`, `sage`, `shimmer`, `marin`, and `cedar`. You may * also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Custom - * voices must be created from audio samples. Voices created from text prompts are supported - * only in Live. + * voices must be created from audio samples. */ @JsonDeserialize(using = Voice.Deserializer::class) @JsonSerialize(using = Voice.Serializer::class) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeAudioConfigOutput.kt b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeAudioConfigOutput.kt index 3aeedf4bd..f2ecdd54d 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeAudioConfigOutput.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeAudioConfigOutput.kt @@ -73,8 +73,7 @@ private constructor( * `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a * custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be * changed during the session once the model has responded with audio at least once. Custom - * voices must be created from audio samples. Voices created from text prompts are supported - * only in Live. We recommend `marin` and `cedar` for best quality. + * voices must be created from audio samples. We recommend `marin` and `cedar` for best quality. * * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -185,8 +184,8 @@ private constructor( * `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also * provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice * cannot be changed during the session once the model has responded with audio at least - * once. Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. We recommend `marin` and `cedar` for best quality. + * once. Custom voices must be created from audio samples. We recommend `marin` and `cedar` + * for best quality. */ fun voice(voice: Voice) = voice(JsonField.of(voice)) @@ -280,8 +279,7 @@ private constructor( * `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also provide a * custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice cannot be * changed during the session once the model has responded with audio at least once. Custom - * voices must be created from audio samples. Voices created from text prompts are supported - * only in Live. We recommend `marin` and `cedar` for best quality. + * voices must be created from audio samples. We recommend `marin` and `cedar` for best quality. */ @JsonDeserialize(using = Voice.Deserializer::class) @JsonSerialize(using = Voice.Serializer::class) diff --git a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeResponseCreateAudioOutput.kt b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeResponseCreateAudioOutput.kt index d499ce6a5..43f3c6072 100644 --- a/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeResponseCreateAudioOutput.kt +++ b/openai-java-core/src/main/kotlin/com/openai/models/realtime/RealtimeResponseCreateAudioOutput.kt @@ -191,8 +191,8 @@ private constructor( * `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also * provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice * cannot be changed during the session once the model has responded with audio at least - * once. Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. We recommend `marin` and `cedar` for best quality. + * once. Custom voices must be created from audio samples. We recommend `marin` and `cedar` + * for best quality. * * @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the * server responded with an unexpected value). @@ -276,9 +276,8 @@ private constructor( * `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may * also provide a custom voice object with an `id`, for example `{ "id": "voice_1234" * }`. Voice cannot be changed during the session once the model has responded with - * audio at least once. Custom voices must be created from audio samples. Voices created - * from text prompts are supported only in Live. We recommend `marin` and `cedar` for - * best quality. + * audio at least once. Custom voices must be created from audio samples. We recommend + * `marin` and `cedar` for best quality. */ fun voice(voice: Voice) = voice(JsonField.of(voice)) @@ -372,8 +371,8 @@ private constructor( * `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, and `cedar`. You may also * provide a custom voice object with an `id`, for example `{ "id": "voice_1234" }`. Voice * cannot be changed during the session once the model has responded with audio at least - * once. Custom voices must be created from audio samples. Voices created from text prompts - * are supported only in Live. We recommend `marin` and `cedar` for best quality. + * once. Custom voices must be created from audio samples. We recommend `marin` and `cedar` + * for best quality. */ @JsonDeserialize(using = Voice.Deserializer::class) @JsonSerialize(using = Voice.Serializer::class) diff --git a/openai-java-core/src/main/kotlin/com/openai/services/async/audio/VoiceServiceAsync.kt b/openai-java-core/src/main/kotlin/com/openai/services/async/audio/VoiceServiceAsync.kt index 70acd8c73..7ad0391de 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/async/audio/VoiceServiceAsync.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/async/audio/VoiceServiceAsync.kt @@ -27,15 +27,15 @@ interface VoiceServiceAsync { fun withOptions(modifier: Consumer): VoiceServiceAsync /** - * Creates a voice from a text prompt or from a consent recording and an audio sample. + * Create a custom voice you can use for audio output (for example, in Text-to-Speech and the + * Realtime API). This requires an audio sample and a previously uploaded consent recording. * - * For prompt-based creation, send `type: "prompt"` with a `name` and `prompt` as JSON or - * multipart form data. For creation from an audio sample, send `type: "audio_sample"` with a - * `name`, `audio_sample`, and `consent` recording ID as multipart form data. The type defaults - * to `audio_sample` when omitted. + * Send `name`, `audio_sample`, and the `consent` recording ID as multipart form data. The + * optional `type` defaults to `audio_sample`. * - * Returns the saved voice's metadata. Voices created from text prompts are supported only in - * Live, not in Realtime or the speech endpoint. The response does not include preview audio. + * Returns the saved voice's metadata. See the + * [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) + * for requirements and best practices. Custom voices are limited to eligible customers. */ fun create(params: VoiceCreateParams): CompletableFuture = create(params, RequestOptions.none()) diff --git a/openai-java-core/src/main/kotlin/com/openai/services/blocking/audio/VoiceService.kt b/openai-java-core/src/main/kotlin/com/openai/services/blocking/audio/VoiceService.kt index 6fc213694..e8c5517a5 100644 --- a/openai-java-core/src/main/kotlin/com/openai/services/blocking/audio/VoiceService.kt +++ b/openai-java-core/src/main/kotlin/com/openai/services/blocking/audio/VoiceService.kt @@ -27,15 +27,15 @@ interface VoiceService { fun withOptions(modifier: Consumer): VoiceService /** - * Creates a voice from a text prompt or from a consent recording and an audio sample. + * Create a custom voice you can use for audio output (for example, in Text-to-Speech and the + * Realtime API). This requires an audio sample and a previously uploaded consent recording. * - * For prompt-based creation, send `type: "prompt"` with a `name` and `prompt` as JSON or - * multipart form data. For creation from an audio sample, send `type: "audio_sample"` with a - * `name`, `audio_sample`, and `consent` recording ID as multipart form data. The type defaults - * to `audio_sample` when omitted. + * Send `name`, `audio_sample`, and the `consent` recording ID as multipart form data. The + * optional `type` defaults to `audio_sample`. * - * Returns the saved voice's metadata. Voices created from text prompts are supported only in - * Live, not in Realtime or the speech endpoint. The response does not include preview audio. + * Returns the saved voice's metadata. See the + * [custom voices guide](https://developers.openai.com/api/docs/guides/text-to-speech#custom-voices) + * for requirements and best practices. Custom voices are limited to eligible customers. */ fun create(params: VoiceCreateParams): Voice = create(params, RequestOptions.none()) diff --git a/openai-java-core/src/test/kotlin/com/openai/core/http/UnionUploadStreamingTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/http/UnionUploadStreamingTest.kt index 37bbb69bb..f1823c4a5 100644 --- a/openai-java-core/src/test/kotlin/com/openai/core/http/UnionUploadStreamingTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/core/http/UnionUploadStreamingTest.kt @@ -69,23 +69,11 @@ internal class UnionUploadStreamingTest { } @ParameterizedTest - @CsvSource("false,sample", "true,sample", "false,prompt", "true,prompt") - fun voiceUnionUploadsPreserveMultipartFieldsAndStreaming(async: Boolean, kind: String) { + @ValueSource(booleans = [false, true]) + fun voiceUploadsPreserveMultipartFieldsAndStreaming(async: Boolean) { val payload = byteArrayOf(0, 1, -1, 13, 10, 65, -128) val stream = GuardedStream(payload) - val params = - if (kind == "sample") voiceParams(stream) - else - VoiceCreateParams.builder() - .body( - VoiceCreateParams.Body.Prompt.builder() - .name("Synthetic voice") - .prompt("A calm synthetic narrator") - .model("auto") - .scriptHint("Welcome aboard") - .build() - ) - .build() + val params = voiceParams(stream) client( checkRequest = { assertThat(it.method).isEqualTo(HttpMethod.POST) @@ -94,12 +82,10 @@ internal class UnionUploadStreamingTest { ) { body -> assertThat(body.contentType()).startsWith("multipart/form-data; boundary=") val output = ByteArrayOutputStream() - if (kind == "sample") { - assertThat(stream.closed).isFalse() - assertThat(body.contentLength()).isEqualTo(-1L) - assertThat(body.repeatable()).isFalse() - stream.allowReads = true - } + assertThat(stream.closed).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + assertThat(body.repeatable()).isFalse() + stream.allowReads = true body.use { it.writeTo(output) } val boundary = checkNotNull(body.contentType()).substringAfter("boundary=") val parts = output.toString("ISO-8859-1").split("--$boundary") @@ -109,28 +95,19 @@ internal class UnionUploadStreamingTest { .substringAfter("\r\n\r\n") .removeSuffix("\r\n") assertThat(value("name")).isEqualTo("Synthetic voice") - if (kind == "sample") { - assertThat(value("consent")).isEqualTo("cons_synthetic") - assertThat(parts.single { it.contains("name=\"audio_sample\"") }) - .contains("Content-Type: application/octet-stream\r\n") - assertThat(value("audio_sample").toByteArray(Charsets.ISO_8859_1)) - .isEqualTo(payload) - assertThat(parts.none { it.contains("name=\"prompt\"") }).isTrue() - assertThat(stream.closeCount).isEqualTo(1) - } else { - assertThat(value("type")).isEqualTo("prompt") - assertThat(value("prompt")).isEqualTo("A calm synthetic narrator") - assertThat(value("model")).isEqualTo("auto") - assertThat(value("script_hint")).isEqualTo("Welcome aboard") - assertThat(parts.none { it.contains("name=\"audio_sample\"") }).isTrue() - assertThat(parts.none { it.contains("name=\"consent\"") }).isTrue() - } + assertThat(value("consent")).isEqualTo("cons_synthetic") + assertThat(parts.single { it.contains("name=\"audio_sample\"") }) + .contains("Content-Type: application/octet-stream\r\n") + assertThat(value("audio_sample").toByteArray(Charsets.ISO_8859_1)) + .isEqualTo(payload) + assertThat(parts.none { it.contains("name=\"prompt\"") }).isTrue() + assertThat(stream.closeCount).isEqualTo(1) } .useClient { if (async) it.async().audio().voices().create(params).get(10, TimeUnit.SECONDS) else it.audio().voices().withRawResponse().create(params).close() } - if (kind == "sample") assertThat(stream.closeCount).isEqualTo(1) + assertThat(stream.closeCount).isEqualTo(1) } @Test