Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private constructor(
private var content: JsonField<Content>? = null
private var role: JsonField<Role>? = null
private var phase: JsonField<Phase> = JsonMissing.of()
private var type: JsonField<Type> = JsonMissing.of()
private var type: JsonField<Type> = JsonField.of(Type.MESSAGE)
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()

@JvmSynthetic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1218,18 +1218,33 @@ private constructor(

override fun ObjectCodec.deserialize(node: JsonNode): ResponseInputItem {
val json = JsonValue.fromJsonNode(node)
val type = json.asObject().getOrNull()?.get("type")?.asString()?.getOrNull()
val jsonObject = json.asObject().getOrNull()
val type = jsonObject?.get("type")?.asString()?.getOrNull()

when (type) {
"message" -> {
val role = jsonObject?.get("role")?.asString()?.getOrNull()
val hasPhase = jsonObject?.containsKey("phase") == true
val hasStructuredContent =
jsonObject?.get("content")?.asArray()?.getOrNull() != null
val isMessageRole = role == "user" || role == "system" || role == "developer"
// These variants share a discriminator and become wire-identical in this
// shape. Prefer the narrower Message; string content, assistant roles, and
// phase-bearing messages remain EasyInputMessage.
val preferMessage = hasStructuredContent && isMessageRole && !hasPhase
val messageMatch =
tryDeserialize(node, jacksonTypeRef<Message>())?.let {
ResponseInputItem(message = it, _json = json)
}
val easyInputMessageMatch =
tryDeserialize(node, jacksonTypeRef<EasyInputMessage>())?.let {
ResponseInputItem(easyInputMessage = it, _json = json)
}

val bestMatches =
sequenceOf(
tryDeserialize(node, jacksonTypeRef<EasyInputMessage>())?.let {
ResponseInputItem(easyInputMessage = it, _json = json)
},
tryDeserialize(node, jacksonTypeRef<Message>())?.let {
ResponseInputItem(message = it, _json = json)
},
if (preferMessage) messageMatch else easyInputMessageMatch,
if (preferMessage) easyInputMessageMatch else messageMatch,
tryDeserialize(node, jacksonTypeRef<ResponseOutputMessage>())?.let {
ResponseInputItem(responseOutputMessage = it, _json = json)
},
Expand Down Expand Up @@ -1572,7 +1587,7 @@ private constructor(
private var content: JsonField<MutableList<ResponseInputContent>>? = null
private var role: JsonField<Role>? = null
private var status: JsonField<Status> = JsonMissing.of()
private var type: JsonField<Type> = JsonMissing.of()
private var type: JsonField<Type> = JsonField.of(Type.MESSAGE)
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()

@JvmSynthetic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ internal class ResponseInputItemTest {
assertThat(roundtrippedResponseInputItem).isEqualTo(responseInputItem)
}

@Test
fun ofEasyInputMessageRoundtripWithoutExplicitType() {
val jsonMapper = jsonMapper()
val responseInputItem =
ResponseInputItem.ofEasyInputMessage(
EasyInputMessage.builder()
.content("string")
.role(EasyInputMessage.Role.USER)
.build()
)

val roundtrippedResponseInputItem =
jsonMapper.readValue(
jsonMapper.writeValueAsString(responseInputItem),
jacksonTypeRef<ResponseInputItem>(),
)

assertThat(roundtrippedResponseInputItem).isEqualTo(responseInputItem)
}

@Test
fun ofMessage() {
val message =
Expand Down Expand Up @@ -164,6 +184,78 @@ internal class ResponseInputItemTest {
assertThat(roundtrippedResponseInputItem).isEqualTo(responseInputItem)
}

@Test
fun ofMessageRoundtripWithoutExplicitType() {
val jsonMapper = jsonMapper()
val responseInputItem =
ResponseInputItem.ofMessage(
ResponseInputItem.Message.builder()
.addInputTextContent("Test")
.role(ResponseInputItem.Message.Role.USER)
.build()
)

val roundtrippedResponseInputItem =
jsonMapper.readValue(
jsonMapper.writeValueAsString(responseInputItem),
jacksonTypeRef<ResponseInputItem>(),
)

assertThat(roundtrippedResponseInputItem).isEqualTo(responseInputItem)
}

@Test
fun ofStructuredAssistantEasyInputMessageRoundtripWithoutPhase() {
val jsonMapper = jsonMapper()
val responseInputItem =
ResponseInputItem.ofEasyInputMessage(
EasyInputMessage.builder()
.contentOfResponseInputMessageContentList(
listOf(
ResponseInputContent.ofInputText(
ResponseInputText.builder().text("text").build()
)
)
)
.role(EasyInputMessage.Role.ASSISTANT)
.type(EasyInputMessage.Type.MESSAGE)
.build()
)

val roundtrippedResponseInputItem =
jsonMapper.readValue(
jsonMapper.writeValueAsString(responseInputItem),
jacksonTypeRef<ResponseInputItem>(),
)

assertThat(roundtrippedResponseInputItem).isEqualTo(responseInputItem)
}

@Test
fun deserializesStructuredNonAssistantMessagePayloadAsMessage() {
val jsonMapper = jsonMapper()
val responseInputItem =
jsonMapper.readValue(
"""
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "text"
}
]
}
"""
.trimIndent(),
jacksonTypeRef<ResponseInputItem>(),
)

assertThat(responseInputItem.message()).isPresent
assertThat(responseInputItem.easyInputMessage()).isEmpty
}

@Test
fun ofResponseOutputMessage() {
val responseOutputMessage =
Expand Down