Skip to content

Commit 9de73d2

Browse files
feat(api): add phase field to conversations Message
1 parent 15ae242 commit 9de73d2

5 files changed

Lines changed: 190 additions & 6 deletions

File tree

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 151
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-2fab88288cbbe872f5d61d1d47da2286662a123b4312bc7fc36addba6607cd67.yml
3-
openapi_spec_hash: a7ee80374e409ed9ecc8ea2e3cd31071
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/openai%2Fopenai-89e54b8e2c185d30e869f73e7798308d56a6a835a675d54628dd86836f147879.yml
3+
openapi_spec_hash: 85b0dd465aa1a034f2764b0758671f21
44
config_hash: 5635033cdc8c930255f8b529a78de722

openai-java-core/src/main/kotlin/com/openai/models/conversations/Message.kt

Lines changed: 180 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ private constructor(
4444
private val role: JsonField<Role>,
4545
private val status: JsonField<Status>,
4646
private val type: JsonValue,
47+
private val phase: JsonField<Phase>,
4748
private val additionalProperties: MutableMap<String, JsonValue>,
4849
) {
4950

@@ -56,7 +57,8 @@ private constructor(
5657
@JsonProperty("role") @ExcludeMissing role: JsonField<Role> = JsonMissing.of(),
5758
@JsonProperty("status") @ExcludeMissing status: JsonField<Status> = JsonMissing.of(),
5859
@JsonProperty("type") @ExcludeMissing type: JsonValue = JsonMissing.of(),
59-
) : this(id, content, role, status, type, mutableMapOf())
60+
@JsonProperty("phase") @ExcludeMissing phase: JsonField<Phase> = JsonMissing.of(),
61+
) : this(id, content, role, status, type, phase, mutableMapOf())
6062

6163
/**
6264
* The unique ID of the message.
@@ -105,6 +107,17 @@ private constructor(
105107
*/
106108
@JsonProperty("type") @ExcludeMissing fun _type(): JsonValue = type
107109

110+
/**
111+
* Labels an `assistant` message as intermediate commentary (`commentary`) or the final answer
112+
* (`final_answer`). For models like `gpt-5.3-codex` and beyond, when sending follow-up
113+
* requests, preserve and resend phase on all assistant messages — dropping it can degrade
114+
* performance. Not used for user messages.
115+
*
116+
* @throws OpenAIInvalidDataException if the JSON field has an unexpected type (e.g. if the
117+
* server responded with an unexpected value).
118+
*/
119+
fun phase(): Optional<Phase> = phase.getOptional("phase")
120+
108121
/**
109122
* Returns the raw JSON value of [id].
110123
*
@@ -133,6 +146,13 @@ private constructor(
133146
*/
134147
@JsonProperty("status") @ExcludeMissing fun _status(): JsonField<Status> = status
135148

149+
/**
150+
* Returns the raw JSON value of [phase].
151+
*
152+
* Unlike [phase], this method doesn't throw if the JSON field has an unexpected type.
153+
*/
154+
@JsonProperty("phase") @ExcludeMissing fun _phase(): JsonField<Phase> = phase
155+
136156
@JsonAnySetter
137157
private fun putAdditionalProperty(key: String, value: JsonValue) {
138158
additionalProperties.put(key, value)
@@ -169,6 +189,7 @@ private constructor(
169189
private var role: JsonField<Role>? = null
170190
private var status: JsonField<Status>? = null
171191
private var type: JsonValue = JsonValue.from("message")
192+
private var phase: JsonField<Phase> = JsonMissing.of()
172193
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
173194

174195
@JvmSynthetic
@@ -178,6 +199,7 @@ private constructor(
178199
role = message.role
179200
status = message.status
180201
type = message.type
202+
phase = message.phase
181203
additionalProperties = message.additionalProperties.toMutableMap()
182204
}
183205

@@ -359,6 +381,25 @@ private constructor(
359381
*/
360382
fun type(type: JsonValue) = apply { this.type = type }
361383

384+
/**
385+
* Labels an `assistant` message as intermediate commentary (`commentary`) or the final
386+
* answer (`final_answer`). For models like `gpt-5.3-codex` and beyond, when sending
387+
* follow-up requests, preserve and resend phase on all assistant messages — dropping it can
388+
* degrade performance. Not used for user messages.
389+
*/
390+
fun phase(phase: Phase?) = phase(JsonField.ofNullable(phase))
391+
392+
/** Alias for calling [Builder.phase] with `phase.orElse(null)`. */
393+
fun phase(phase: Optional<Phase>) = phase(phase.getOrNull())
394+
395+
/**
396+
* Sets [Builder.phase] to an arbitrary JSON value.
397+
*
398+
* You should usually call [Builder.phase] with a well-typed [Phase] value instead. This
399+
* method is primarily for setting the field to an undocumented or not yet supported value.
400+
*/
401+
fun phase(phase: JsonField<Phase>) = apply { this.phase = phase }
402+
362403
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
363404
this.additionalProperties.clear()
364405
putAllAdditionalProperties(additionalProperties)
@@ -400,6 +441,7 @@ private constructor(
400441
checkRequired("role", role),
401442
checkRequired("status", status),
402443
type,
444+
phase,
403445
additionalProperties.toMutableMap(),
404446
)
405447
}
@@ -420,6 +462,7 @@ private constructor(
420462
throw OpenAIInvalidDataException("'type' is invalid, received $it")
421463
}
422464
}
465+
phase().ifPresent { it.validate() }
423466
validated = true
424467
}
425468

@@ -442,7 +485,8 @@ private constructor(
442485
(content.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
443486
(role.asKnown().getOrNull()?.validity() ?: 0) +
444487
(status.asKnown().getOrNull()?.validity() ?: 0) +
445-
type.let { if (it == JsonValue.from("message")) 1 else 0 }
488+
type.let { if (it == JsonValue.from("message")) 1 else 0 } +
489+
(phase.asKnown().getOrNull()?.validity() ?: 0)
446490

447491
/** A content part that makes up an input or output item. */
448492
@JsonDeserialize(using = Content.Deserializer::class)
@@ -1379,6 +1423,137 @@ private constructor(
13791423
override fun toString() = value.toString()
13801424
}
13811425

1426+
/**
1427+
* Labels an `assistant` message as intermediate commentary (`commentary`) or the final answer
1428+
* (`final_answer`). For models like `gpt-5.3-codex` and beyond, when sending follow-up
1429+
* requests, preserve and resend phase on all assistant messages — dropping it can degrade
1430+
* performance. Not used for user messages.
1431+
*/
1432+
class Phase @JsonCreator private constructor(private val value: JsonField<String>) : Enum {
1433+
1434+
/**
1435+
* Returns this class instance's raw value.
1436+
*
1437+
* This is usually only useful if this instance was deserialized from data that doesn't
1438+
* match any known member, and you want to know that value. For example, if the SDK is on an
1439+
* older version than the API, then the API may respond with new members that the SDK is
1440+
* unaware of.
1441+
*/
1442+
@com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField<String> = value
1443+
1444+
companion object {
1445+
1446+
@JvmField val COMMENTARY = of("commentary")
1447+
1448+
@JvmField val FINAL_ANSWER = of("final_answer")
1449+
1450+
@JvmStatic fun of(value: String) = Phase(JsonField.of(value))
1451+
}
1452+
1453+
/** An enum containing [Phase]'s known values. */
1454+
enum class Known {
1455+
COMMENTARY,
1456+
FINAL_ANSWER,
1457+
}
1458+
1459+
/**
1460+
* An enum containing [Phase]'s known values, as well as an [_UNKNOWN] member.
1461+
*
1462+
* An instance of [Phase] can contain an unknown value in a couple of cases:
1463+
* - It was deserialized from data that doesn't match any known member. For example, if the
1464+
* SDK is on an older version than the API, then the API may respond with new members that
1465+
* the SDK is unaware of.
1466+
* - It was constructed with an arbitrary value using the [of] method.
1467+
*/
1468+
enum class Value {
1469+
COMMENTARY,
1470+
FINAL_ANSWER,
1471+
/** An enum member indicating that [Phase] was instantiated with an unknown value. */
1472+
_UNKNOWN,
1473+
}
1474+
1475+
/**
1476+
* Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN]
1477+
* if the class was instantiated with an unknown value.
1478+
*
1479+
* Use the [known] method instead if you're certain the value is always known or if you want
1480+
* to throw for the unknown case.
1481+
*/
1482+
fun value(): Value =
1483+
when (this) {
1484+
COMMENTARY -> Value.COMMENTARY
1485+
FINAL_ANSWER -> Value.FINAL_ANSWER
1486+
else -> Value._UNKNOWN
1487+
}
1488+
1489+
/**
1490+
* Returns an enum member corresponding to this class instance's value.
1491+
*
1492+
* Use the [value] method instead if you're uncertain the value is always known and don't
1493+
* want to throw for the unknown case.
1494+
*
1495+
* @throws OpenAIInvalidDataException if this class instance's value is a not a known
1496+
* member.
1497+
*/
1498+
fun known(): Known =
1499+
when (this) {
1500+
COMMENTARY -> Known.COMMENTARY
1501+
FINAL_ANSWER -> Known.FINAL_ANSWER
1502+
else -> throw OpenAIInvalidDataException("Unknown Phase: $value")
1503+
}
1504+
1505+
/**
1506+
* Returns this class instance's primitive wire representation.
1507+
*
1508+
* This differs from the [toString] method because that method is primarily for debugging
1509+
* and generally doesn't throw.
1510+
*
1511+
* @throws OpenAIInvalidDataException if this class instance's value does not have the
1512+
* expected primitive type.
1513+
*/
1514+
fun asString(): String =
1515+
_value().asString().orElseThrow { OpenAIInvalidDataException("Value is not a String") }
1516+
1517+
private var validated: Boolean = false
1518+
1519+
fun validate(): Phase = apply {
1520+
if (validated) {
1521+
return@apply
1522+
}
1523+
1524+
known()
1525+
validated = true
1526+
}
1527+
1528+
fun isValid(): Boolean =
1529+
try {
1530+
validate()
1531+
true
1532+
} catch (e: OpenAIInvalidDataException) {
1533+
false
1534+
}
1535+
1536+
/**
1537+
* Returns a score indicating how many valid values are contained in this object
1538+
* recursively.
1539+
*
1540+
* Used for best match union deserialization.
1541+
*/
1542+
@JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
1543+
1544+
override fun equals(other: Any?): Boolean {
1545+
if (this === other) {
1546+
return true
1547+
}
1548+
1549+
return other is Phase && value == other.value
1550+
}
1551+
1552+
override fun hashCode() = value.hashCode()
1553+
1554+
override fun toString() = value.toString()
1555+
}
1556+
13821557
override fun equals(other: Any?): Boolean {
13831558
if (this === other) {
13841559
return true
@@ -1390,15 +1565,16 @@ private constructor(
13901565
role == other.role &&
13911566
status == other.status &&
13921567
type == other.type &&
1568+
phase == other.phase &&
13931569
additionalProperties == other.additionalProperties
13941570
}
13951571

13961572
private val hashCode: Int by lazy {
1397-
Objects.hash(id, content, role, status, type, additionalProperties)
1573+
Objects.hash(id, content, role, status, type, phase, additionalProperties)
13981574
}
13991575

14001576
override fun hashCode(): Int = hashCode
14011577

14021578
override fun toString() =
1403-
"Message{id=$id, content=$content, role=$role, status=$status, type=$type, additionalProperties=$additionalProperties}"
1579+
"Message{id=$id, content=$content, role=$role, status=$status, type=$type, phase=$phase, additionalProperties=$additionalProperties}"
14041580
}

openai-java-core/src/test/kotlin/com/openai/models/conversations/MessageTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class MessageTest {
1818
.addInputTextContent("text")
1919
.role(Message.Role.UNKNOWN)
2020
.status(Message.Status.IN_PROGRESS)
21+
.phase(Message.Phase.COMMENTARY)
2122
.build()
2223

2324
assertThat(message.id()).isEqualTo("id")
@@ -27,6 +28,7 @@ internal class MessageTest {
2728
)
2829
assertThat(message.role()).isEqualTo(Message.Role.UNKNOWN)
2930
assertThat(message.status()).isEqualTo(Message.Status.IN_PROGRESS)
31+
assertThat(message.phase()).contains(Message.Phase.COMMENTARY)
3032
}
3133

3234
@Test
@@ -38,6 +40,7 @@ internal class MessageTest {
3840
.addInputTextContent("text")
3941
.role(Message.Role.UNKNOWN)
4042
.status(Message.Status.IN_PROGRESS)
43+
.phase(Message.Phase.COMMENTARY)
4144
.build()
4245

4346
val roundtrippedMessage =

openai-java-core/src/test/kotlin/com/openai/models/conversations/items/ConversationItemListTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal class ConversationItemListTest {
2020
.addInputTextContent("text")
2121
.role(Message.Role.UNKNOWN)
2222
.status(Message.Status.IN_PROGRESS)
23+
.phase(Message.Phase.COMMENTARY)
2324
.build()
2425
)
2526
.firstId("first_id")
@@ -35,6 +36,7 @@ internal class ConversationItemListTest {
3536
.addInputTextContent("text")
3637
.role(Message.Role.UNKNOWN)
3738
.status(Message.Status.IN_PROGRESS)
39+
.phase(Message.Phase.COMMENTARY)
3840
.build()
3941
)
4042
)
@@ -54,6 +56,7 @@ internal class ConversationItemListTest {
5456
.addInputTextContent("text")
5557
.role(Message.Role.UNKNOWN)
5658
.status(Message.Status.IN_PROGRESS)
59+
.phase(Message.Phase.COMMENTARY)
5760
.build()
5861
)
5962
.firstId("first_id")

openai-java-core/src/test/kotlin/com/openai/models/conversations/items/ConversationItemTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ internal class ConversationItemTest {
4545
.addInputTextContent("text")
4646
.role(Message.Role.UNKNOWN)
4747
.status(Message.Status.IN_PROGRESS)
48+
.phase(Message.Phase.COMMENTARY)
4849
.build()
4950

5051
val conversationItem = ConversationItem.ofMessage(message)
@@ -86,6 +87,7 @@ internal class ConversationItemTest {
8687
.addInputTextContent("text")
8788
.role(Message.Role.UNKNOWN)
8889
.status(Message.Status.IN_PROGRESS)
90+
.phase(Message.Phase.COMMENTARY)
8991
.build()
9092
)
9193

0 commit comments

Comments
 (0)