diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt b/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt index 9aa57d23..21330750 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/bridge/DomainEventMapper.kt @@ -82,6 +82,8 @@ suspend fun domainEventToServerMessage( turnId = p.turnId, role = p.role.name, content = p.content, + latencyMs = p.latencyMs, + totalTokens = p.tokensUsed?.totalTokens, sequence = seq, sessionSequence = sessionSequence, ) diff --git a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt index cd8e9cd5..9c249ebf 100644 --- a/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt +++ b/apps/server/src/main/kotlin/com/correx/apps/server/protocol/ServerMessage.kt @@ -67,6 +67,8 @@ sealed interface ServerMessage { val turnId: String, val role: String, val content: String, + val latencyMs: Long? = null, + val totalTokens: Int? = null, override val sequence: Long, override val sessionSequence: Long, ) : ServerMessage, SessionMessage diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/bridge/DomainEventMapperTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/bridge/DomainEventMapperTest.kt index a0a0278e..577c3044 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/bridge/DomainEventMapperTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/bridge/DomainEventMapperTest.kt @@ -113,7 +113,14 @@ class DomainEventMapperTest { ) val result = domainEventToServerMessage(event, noopStore, sessionSequence = 3L) assertEquals( - ServerMessage.ChatTurn(sessionId, "turn-1", "ROUTER", "hello", event.sequence, 3L), + ServerMessage.ChatTurn( + sessionId = sessionId, + turnId = "turn-1", + role = "ROUTER", + content = "hello", + sequence = event.sequence, + sessionSequence = 3L, + ), result, ) } diff --git a/apps/server/src/test/kotlin/com/correx/apps/server/protocol/ServerMessageSerializationTest.kt b/apps/server/src/test/kotlin/com/correx/apps/server/protocol/ServerMessageSerializationTest.kt index 29202b21..3fe6336e 100644 --- a/apps/server/src/test/kotlin/com/correx/apps/server/protocol/ServerMessageSerializationTest.kt +++ b/apps/server/src/test/kotlin/com/correx/apps/server/protocol/ServerMessageSerializationTest.kt @@ -45,6 +45,40 @@ class ServerMessageSerializationTest { assert(jsonStr.contains("\"content\":\"hi there\"")) { "expected content" } } + @Test + fun `ChatTurn with metrics encodes latencyMs and totalTokens`() { + val msg = ServerMessage.ChatTurn( + sessionId = SessionId("sess-1"), + turnId = "t9", + role = "ROUTER", + content = "hi", + latencyMs = 3000, + totalTokens = 943, + sequence = 7, + sessionSequence = 2, + ) + val jsonStr = ProtocolSerializer.encodeServerMessage(msg) + assert(jsonStr.contains("\"type\":\"chat.turn\"")) { "expected type=chat.turn" } + assert(jsonStr.contains("\"latencyMs\":3000")) { "expected latencyMs=3000" } + assert(jsonStr.contains("\"totalTokens\":943")) { "expected totalTokens=943" } + } + + @Test + fun `ChatTurn without metrics encodes latencyMs and totalTokens as absent or null`() { + val msg = ServerMessage.ChatTurn( + sessionId = SessionId("sess-1"), + turnId = "t10", + role = "USER", + content = "hello", + sequence = 8, + sessionSequence = 3, + ) + val jsonStr = ProtocolSerializer.encodeServerMessage(msg) + assert(jsonStr.contains("\"type\":\"chat.turn\"")) { "expected type=chat.turn" } + assert(!jsonStr.contains("\"latencyMs\":3000")) { "latencyMs should not be 3000" } + assert(!jsonStr.contains("\"totalTokens\":943")) { "totalTokens should not be 943" } + } + @Test fun `SnapshotComplete encodes with only type field`() { val msg = ServerMessage.SnapshotComplete