diff options
| author | Adam Malczewski <[email protected]> | 2026-06-07 00:02:32 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-07 00:02:32 +0900 |
| commit | 312ed5a9fe0d7f2660a66d61baf429b8e021ffb1 (patch) | |
| tree | dad46b8b88a6bc5629d50f18912ff944eaef4e95 /src/core/chunks/selectors.ts | |
| parent | a062045579de1bd0903f0f2c7f3e742162bcb7cc (diff) | |
| download | dispatch-web-312ed5a9fe0d7f2660a66d61baf429b8e021ffb1.tar.gz dispatch-web-312ed5a9fe0d7f2660a66d61baf429b8e021ffb1.zip | |
Slice 2 wave 1: transcript reducer, wire conformance, ws chat, cache core
- core/chunks: the one pure transcript reducer (foldEvent live deltas +
applyHistory seq-keyed reconcile + selectChunks/selectMessages); 27 tests
- core/wire: FE-side contract-conformance exhaustiveness guards + drift smoke
tests over wire/transport-contract unions (ยง2.9 drift signal); 10 tests
- adapters/ws: additively multiplex chat.send/chat.delta/chat.error on the
existing surface socket (onChat + widened send); surface API unchanged
- features/conversation-cache: pure reconcileCache/nextSinceSeq/selectEvictions
+ ConversationChunkStore port + injected createConversationCache; 26 tests
Verified green: svelte-check 0/0, vitest 169, biome clean, build ok.
Diffstat (limited to 'src/core/chunks/selectors.ts')
| -rw-r--r-- | src/core/chunks/selectors.ts | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/src/core/chunks/selectors.ts b/src/core/chunks/selectors.ts new file mode 100644 index 0000000..8fb832f --- /dev/null +++ b/src/core/chunks/selectors.ts @@ -0,0 +1,51 @@ +import type { ChatMessage, Chunk } from "@dispatch/wire"; +import type { RenderedChunk, TranscriptState } from "./types"; + +/** + * Select all chunks for rendering: committed first (seq order), + * then provisional (seq: null). + */ +export function selectChunks(state: TranscriptState): readonly RenderedChunk[] { + const result: RenderedChunk[] = []; + for (const c of state.committed) { + result.push({ seq: c.seq, role: c.role, chunk: c.chunk, provisional: false }); + } + for (const p of state.provisional) { + result.push({ seq: null, role: p.role, chunk: p.chunk, provisional: true }); + } + if (state.accumulating !== null) { + const chunk: Chunk = + state.accumulating.kind === "text" + ? { type: "text", text: state.accumulating.text } + : { type: "thinking", text: state.accumulating.text }; + result.push({ seq: null, role: "assistant", chunk, provisional: true }); + } + return result; +} + +/** + * Group consecutive same-role rendered chunks into ChatMessages. + */ +export function selectMessages(state: TranscriptState): readonly ChatMessage[] { + const rendered = selectChunks(state); + const first = rendered[0]; + if (first === undefined) return []; + + const messages: ChatMessage[] = []; + let role = first.role; + let chunks: Chunk[] = [first.chunk]; + + for (let i = 1; i < rendered.length; i++) { + const rc = rendered[i]; + if (rc === undefined) continue; + if (rc.role === role) { + chunks.push(rc.chunk); + } else { + messages.push({ role, chunks }); + role = rc.role; + chunks = [rc.chunk]; + } + } + messages.push({ role, chunks }); + return messages; +} |
