* feat(#525): add ReasoningDelta + ReasoningAvailable WsEvent types * feat(#525): parse reasoning.delta + reasoning.available; route through reducer + StreamingState * feat(#525): add reasoning streaming buffer to ChatStreamingController * feat(#525): dispatch ReasoningDelta/ReasoningAvailable in ChatViewModel * feat(#525): render collapsible ReasoningIndicator in chat list * fix(#525): address review — icons+semantics, LocalHermesStatusColors, drop redundant ReasoningAvailable branch * feat(#525): persist reasoningText on model, entity, mapper + Room migration 3->4 * feat(#525): reducer + streaming carry reasoningText onto message * feat(#525): render persisted reasoning above each assistant bubble, drop global indicator * nix: flake * nix: flake --------- Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
1 parent
d04b74ee0a
commit
53b3966931
17 files changed
+441
-33
No files matched your search
@@ -473,6 +473,55 @@ class ChatViewModelTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReasoningStreamingFlow() =
|
||||
runTest {
|
||||
val (viewModel, sessionId) = createViewModelWithSession()
|
||||
|
||||
// 1 — Reasoning available: block becomes visible (no token yet)
|
||||
mockEventsFlow.emit(WsEvent.ReasoningAvailable(sessionId))
|
||||
advanceUntilIdle()
|
||||
assertTrue(viewModel.streamingState.value.isReasoning)
|
||||
|
||||
// 2 — Reasoning delta
|
||||
mockEventsFlow.emit(WsEvent.ReasoningDelta("Let me think", sessionId))
|
||||
advanceUntilIdle()
|
||||
assertEquals("Let me think", viewModel.streamingState.value.reasoningText)
|
||||
|
||||
// 3 — Deeper reasoning
|
||||
mockEventsFlow.emit(WsEvent.ReasoningDelta(" step by step", sessionId))
|
||||
advanceUntilIdle()
|
||||
assertEquals("Let me think step by step", viewModel.streamingState.value.reasoningText)
|
||||
|
||||
// 4 — Thinking still independent
|
||||
mockEventsFlow.emit(WsEvent.ThinkingDelta("thinking", sessionId))
|
||||
advanceUntilIdle()
|
||||
assertTrue(viewModel.streamingState.value.isThinking)
|
||||
assertEquals("thinking", viewModel.streamingState.value.thinkingText)
|
||||
// reasoning untouched
|
||||
assertEquals("Let me think step by step", viewModel.streamingState.value.reasoningText)
|
||||
|
||||
// 5 — Complete: reducer finalizes message, attaching reasoning
|
||||
mockEventsFlow.emit(WsEvent.MessageComplete("The answer is 42", sessionId))
|
||||
advanceUntilIdle()
|
||||
|
||||
assertNull(viewModel.streamingState.value.streamingMessage)
|
||||
assertEquals(2, viewModel.uiState.value.messages.size)
|
||||
assertEquals(
|
||||
"The answer is 42",
|
||||
viewModel.uiState.value.messages[1].content,
|
||||
)
|
||||
// reasoning carried onto the finalized UI message
|
||||
assertEquals(
|
||||
"Let me think step by step",
|
||||
viewModel.uiState.value.messages[1].reasoningText,
|
||||
)
|
||||
// reasoning persisted to the entity (survives reload)
|
||||
val persisted =
|
||||
fakeRepo.dao.getMessagesForSession(sessionId).first { it.role == "ASSISTANT" }
|
||||
assertEquals("Let me think step by step", persisted.reasoningText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToolExecution_finalizesPreviousStreamingMessage() =
|
||||
runTest {
|
||||
|
||||
Reference in new issue
Block a user