diff --git a/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt b/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt index a2f79567..c6b438ff 100644 --- a/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt +++ b/core/events/src/main/kotlin/com/correx/core/events/events/RouterEvents.kt @@ -50,3 +50,16 @@ data class ContextTruncatedEvent( val truncatedLayers: List, val timestampMs: Long, ) : EventPayload + +@Serializable +@SerialName("RouterNarration") +data class RouterNarrationEvent( + val sessionId: SessionId, + val narrationId: String, + val trigger: String, + val stageId: String? = null, + val content: String, + val latencyMs: Long? = null, + val tokensUsed: TokenUsage? = null, + val timestampMs: Long, +) : EventPayload diff --git a/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt b/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt index 3fa3efb0..2fa641d9 100644 --- a/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt +++ b/core/events/src/main/kotlin/com/correx/core/events/serialization/Serialization.kt @@ -9,6 +9,7 @@ import com.correx.core.events.events.ArtifactValidatedEvent import com.correx.core.events.events.ArtifactValidatingEvent import com.correx.core.events.events.ChatSessionStartedEvent import com.correx.core.events.events.ChatTurnEvent +import com.correx.core.events.events.RouterNarrationEvent import com.correx.core.events.events.SessionWorkspaceBoundEvent import com.correx.core.events.events.ContextTruncatedEvent import com.correx.core.events.events.EventPayload @@ -80,6 +81,7 @@ val eventModule = SerializersModule { subclass(RiskAssessedEvent::class) subclass(ChatSessionStartedEvent::class) subclass(ChatTurnEvent::class) + subclass(RouterNarrationEvent::class) subclass(SessionWorkspaceBoundEvent::class) subclass(L3MemoryRetrievedEvent::class) subclass(ContextTruncatedEvent::class) diff --git a/core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt b/core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt new file mode 100644 index 00000000..fbc55490 --- /dev/null +++ b/core/events/src/test/kotlin/com/correx/core/events/serialization/RouterNarrationEventSerializationTest.kt @@ -0,0 +1,45 @@ +package com.correx.core.events.serialization + +import com.correx.core.events.events.EventPayload +import com.correx.core.events.events.RouterNarrationEvent +import com.correx.core.events.types.SessionId +import com.correx.core.inference.TokenUsage +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class RouterNarrationEventSerializationTest { + + private val sample = RouterNarrationEvent( + sessionId = SessionId("sess-1"), + narrationId = "narr-42", + trigger = "stage_start", + stageId = "stage-1", + content = "Router chose the default path.", + latencyMs = 123L, + tokensUsed = TokenUsage(promptTokens = 10, completionTokens = 5), + timestampMs = 1_700_000_000_000L, + ) + + @Test + fun `round-trips as polymorphic EventPayload`() { + val encoded = eventJson.encodeToString(EventPayload.serializer(), sample) + assertTrue(encoded.contains("\"type\":\"RouterNarration\""), "SerialName must be present: $encoded") + val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded) + assertEquals(sample, decoded) + } + + @Test + fun `round-trips with optional fields absent`() { + val minimal = RouterNarrationEvent( + sessionId = SessionId("sess-2"), + narrationId = "narr-0", + trigger = "manual", + content = "Minimal narration.", + timestampMs = 1_000L, + ) + val encoded = eventJson.encodeToString(EventPayload.serializer(), minimal) + val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded) + assertEquals(minimal, decoded) + } +}