Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version: 1
generation_id: b493525e-caf3-4075-95fd-5472a5a73a7a
openapi_spec_hash: c9f4a7dead33c83456491dc0f08be686
openapi_transformed_spec_hash: 04951af56e51b359c1993cc518a3892f
generation_id: a13c16f0-9cec-4e2d-926e-e84061d97ed5
openapi_spec_hash: 7fc94110c3530672f1e30a3eb2b37ac8
openapi_transformed_spec_hash: 151abddd75bd1cf668f2f9129200e28d
config_hash: aae16cbb4861ebb9087774c3bce003a5
codegen_sha: 9ce8f7587db9087dd5c8a8aabc0c01fd740c1b0a
codegen_sha: a9c6e4417121e38b96e483c0ac9dce3437a719a9
codegen_hash: a11a90342d2dca2e925e24b5d8e86ce491b7da65622069f19eecef36f4e93cc7
public_codegen_sha: cded9be95af5e873d65e176e01a00a264ef32682
public_codegen_sha: 2774a763910a830fe9b2946f8ef457384c2fc774
18 changes: 18 additions & 0 deletions api_reference/openapi.transformed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108937,6 +108937,15 @@ x-oaiMeta:
- type: object
key: LiveCommentaryAppendParam
path: <auto>
- type: object
key: LiveDecisionCreateParam
path: <auto>
- type: object
key: LiveDecisionItemCreateParam
path: <auto>
- type: object
key: LiveDecisionItemDeleteParam
path: <auto>
- type: object
key: LiveResponseItemCreateParam
path: <auto>
Expand Down Expand Up @@ -108999,6 +109008,15 @@ x-oaiMeta:
- type: object
key: LiveDelegationCreated
path: <auto>
- type: object
key: LiveDecisionEvent
path: <auto>
- type: object
key: LiveDecisionItemCreated
path: <auto>
- type: object
key: LiveDecisionItemDeleted
path: <auto>
- type: object
key: LiveResponseEvent
path: <auto>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import com.openai.core.http.WebSocketWriteNotAttempted
import com.openai.core.jsonMapper
import com.openai.helpers.LiveSidebandConnection
import com.openai.helpers.LiveWebSocketOptions
import com.openai.models.live.DecisionCreateEvent
import com.openai.models.live.DecisionEvent
import com.openai.models.live.DecisionItemCreateEvent
import com.openai.models.live.DecisionItemDeleteEvent
import com.openai.models.live.InputAudioMuteEvent
import com.openai.models.live.InputAudioUnmuteEvent
import com.openai.models.live.OutputAudioFixturePlayEvent
Expand Down Expand Up @@ -76,6 +80,62 @@ class LiveSidebandConnectionTest {
.baseUrl(server.url("/proxy/v1?inherited=one%2Ftwo&x=a%2Bb").toString())
.build()

@Test
fun decisionCommandsAndResultUseSideband() {
MockWebServer().use { server ->
val peer = Peer()
server.enqueue(MockResponse().withWebSocketUpgrade(peer))
OkHttpClient.builder().build().use { http ->
LiveSidebandConnection.connect(options(server, http), "signaling").use { sideband ->
sideband.send(DecisionCreateEvent.builder().eventId("request").build())
sideband.send(
DecisionItemCreateEvent.builder()
.item(DecisionItemCreateEvent.Item.builder().content("fixture").build())
.build()
)
sideband.send(DecisionItemDeleteEvent.builder().itemId("item-1").build())
for (expected in
listOf(
"""{"type":"decision.create","event_id":"request"}""",
"""{"type":"decision.item.create","item":{"type":"message","role":"user","content":"fixture"}}""",
"""{"type":"decision.item.delete","item_id":"item-1"}""",
)) {
assertThat(mapper.readTree(peer.messages.poll(8, TimeUnit.SECONDS)))
.isEqualTo(mapper.readTree(expected))
}
peer.socket
.get(8, TimeUnit.SECONDS)
.send(
"""
{
"type":"decision.event","event_id":"result","delegation_id":null,
"trigger_id":"trigger-1","source_event_id":"transcript-1",
"source_event_type":"session.input_transcript.delta",
"event":{"type":"decision.completed","decision":{
"model":"fixture-model","answers":[{"type":"predicate","probability":0.75}],
"usage":{"input_tokens":1,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},
"output_tokens":2,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":3}
}}
}
"""
.trimIndent()
)
val result = sideband.receive().asDecisionEvent()
assertThat(result.delegationId()).isEmpty()
assertThat(result.triggerId()).contains("trigger-1")
assertThat(result.sourceEventId()).contains("transcript-1")
assertThat(result.sourceEventType())
.contains(DecisionEvent.SourceEventType.SESSION_INPUT_TRANSCRIPT_DELTA)
assertThat(
result.event().decision().answers().single().asPredicate().probability()
)
.isEqualTo(0.75)
assertThat(result.event().decision().usage().totalTokens()).isEqualTo(3L)
}
}
}
}

@Test
fun replayAndReflectedAudioArriveWithoutAnyFreshStartedEventOrStartupCommand() {
for (async in listOf(false, true)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import com.google.errorprone.annotations.MustBeClosed
import com.openai.core.ClientOptions
import com.openai.core.RequestOptions
import com.openai.models.live.CommentaryAppendEvent
import com.openai.models.live.DecisionCreateEvent
import com.openai.models.live.DecisionItemCreateEvent
import com.openai.models.live.DecisionItemDeleteEvent
import com.openai.models.live.InputAudioMuteEvent
import com.openai.models.live.InputAudioUnmuteEvent
import com.openai.models.live.InstructionsAppendEvent
Expand Down Expand Up @@ -103,6 +106,12 @@ private constructor(

fun send(event: CommentaryAppendEvent) = sendCommand(event)

fun send(event: DecisionCreateEvent) = sendCommand(event)

fun send(event: DecisionItemCreateEvent) = sendCommand(event)

fun send(event: DecisionItemDeleteEvent) = sendCommand(event)

fun send(event: ResponseItemCreateEvent) = sendCommand(event)

fun send(event: ResponseCreateEvent) = sendCommand(event)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.openai.models.live

import com.openai.core.JsonValue
import com.openai.models.live.sessions.SessionAcceptParams
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class DecisionsDelegationVisitorCompatibilityTest {
@Test
fun existingVisitorHandlesDecisionsAsUnknown() {
val visitor =
object : SessionConfig.Delegation.Visitor<JsonValue?> {
override fun visitClient(client: ClientDelegation): JsonValue? = null

override fun visitResponses(
responses: SessionConfig.Delegation.Responses
): JsonValue? = null

override fun unknown(json: JsonValue?) = json
}
val decisions =
SessionConfig.Delegation.Decisions.builder()
.decisions(
DecisionsDelegationConfig.builder()
.model("decision-model")
.addPredicateQuestion("Is the customer asking for help?")
.build()
)
.build()

assertThat(SessionConfig.Delegation.ofDecisions(decisions).accept(visitor))
.isEqualTo(JsonValue.from(decisions))
}

@Test
fun decisionsVisitorMethodsAreJvmDefaults() {
for ((visitor, decisions) in
listOf(
MediaSessionConfig.Delegation.Visitor::class.java to
MediaSessionConfig.Delegation.Decisions::class.java,
SessionConfig.Delegation.Visitor::class.java to
SessionConfig.Delegation.Decisions::class.java,
SessionResource.Delegation.Visitor::class.java to
SessionResource.Delegation.Decisions::class.java,
SessionUpdateConfig.Delegation.Visitor::class.java to
SessionUpdateConfig.Delegation.Decisions::class.java,
SessionAcceptParams.Session.Delegation.Visitor::class.java to
SessionAcceptParams.Session.Delegation.Decisions::class.java,
)) {
assertThat(visitor.getMethod("visitDecisions", decisions).isDefault)
.describedAs("%s.visitDecisions is a JVM default method", visitor.name)
.isTrue()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.openai.models.live

import com.openai.core.jsonMapper
import com.openai.models.live.sessions.SessionAcceptParams
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalStateException
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

internal class LiveDecisionsOmittedListTest {
private val trigger =
DecisionTrigger.builder()
.decisions(DecisionTrigger.Decisions.builder().addPredicateQuestion("Ready?").build())
.eventType(DecisionTrigger.EventType.SESSION_INPUT_TRANSCRIPT_DELTA)
.build()

@Test
fun builtConfigCopyPreservesOmissionUntilFirstTrigger() {
val config = SessionUpdateConfig.builder().build()

assertThat(config.toBuilder().build()._triggers().isMissing()).isTrue()
assertThat(config.toBuilder().addTrigger(trigger).build().triggers())
.contains(listOf(trigger))
}

@Test
fun acceptedSessionCopyAcceptsFirstTrigger() {
val session =
SessionAcceptParams.Session.builder()
.model(SessionAcceptParams.Session.Model.GPT_LIVE_1)
.build()

assertThat(session.toBuilder().addTrigger(trigger).build().triggers())
.contains(listOf(trigger))
}

@Test
fun deserializedConfigCopyPreservesOmissionUntilFirstQuestion() {
val config = jsonMapper().readValue("{}", DecisionsDelegationUpdateConfig::class.java)
val question =
DecisionsDelegationUpdateConfig.Question.ofPredicate(
DecisionsDelegationUpdateConfig.Question.Predicate.builder()
.instructions("Ready?")
.build()
)

assertThat(config.toBuilder().build()._questions().isMissing()).isTrue()
assertThat(config.toBuilder().addQuestion(question).build().questions())
.contains(listOf(question))
}

@ParameterizedTest
@ValueSource(strings = ["null", "\"not a list\""])
fun copiedNonListTriggerValueRejectsAppend(jsonValue: String) {
val config =
jsonMapper().readValue("""{"triggers":$jsonValue}""", SessionUpdateConfig::class.java)

assertThatIllegalStateException().isThrownBy { config.toBuilder().addTrigger(trigger) }
}
}
Loading