feat: render display_kind timeline markers as chips, not user bubbles (#904) (#909)

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-15 14:06:51 +03:00
1 parent 85087c70c7
commit 4be1e965f2
16 files changed
+438 -11

No files matched your search

@@ -161,4 +161,36 @@ class ChatMessageMapperTest {
assertEquals("r", roundTripped.reasoningText)
}
@Test
fun roundTripPreservesDisplayKind() {
val ui =
ChatMessage(
id = "msg-4",
role = MessageRole.USER,
content = "[System: ...changed to gpt-5...]",
displayKind = "model_switch",
timestamp = 4000L,
)
val roundTripped = ui.toEntity("s").toUiModel()
assertEquals("model_switch", roundTripped.displayKind)
assertEquals(MessageRole.USER, roundTripped.role)
}
@Test
fun roundTripWithoutDisplayKindStaysNull() {
val ui =
ChatMessage(
id = "msg-5",
role = MessageRole.USER,
content = "plain user text",
timestamp = 5000L,
)
val roundTripped = ui.toEntity("s").toUiModel()
assertNull(roundTripped.displayKind)
}
}
@@ -4,6 +4,7 @@ import com.m57.hermescontrol.data.remote.OkHttpProvider
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import org.junit.Assert.assertEquals
@@ -420,6 +421,49 @@ class ModelSerializationTest {
assertNull(response.disk?.free_mb)
}
@Test
fun testSessionMessageDeserialization_displayKindMarker() {
val jsonStr =
"""
{
"id": 42,
"role": "user",
"content": "[System: The active model for this chat has changed to gpt-5 via provider openai. From this point forward, use this runtime metadata when answering questions about what model/provider is active.]",
"timestamp": 1755260000000,
"display_kind": "model_switch",
"display_metadata": {
"delegation_id": "dlg-1",
"task_count": 3
}
}
""".trimIndent()
val message = json.decodeFromString<SessionMessage>(jsonStr)
assertEquals(42, message.id)
assertEquals("user", message.role)
assertEquals("model_switch", message.display_kind)
val metadata = message.display_metadata
assertNotNull(metadata)
val delegationId =
(metadata as? JsonObject)?.get("delegation_id") as? JsonPrimitive
assertEquals("dlg-1", delegationId?.content)
}
@Test
fun testSessionMessageDeserialization_missingDisplayKind() {
val jsonStr =
"""
{
"id": 7,
"role": "assistant",
"content": "hi"
}
""".trimIndent()
val message = json.decodeFromString<SessionMessage>(jsonStr)
assertEquals(7, message.id)
assertNull(message.display_kind)
assertNull(message.display_metadata)
}
@Test
fun testStatusResponseDeserialization_typeMismatchInGatewayPlatforms() {
val jsonStr =