summaryrefslogtreecommitdiffhomepage
path: root/src/core/chunks/reducer.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-10-22 17:51:15 +0900
committerAdam Malczewski <[email protected]>2026-10-22 17:51:15 +0900
commit75680eac7071c6968eaa5470686ad102a82103db (patch)
treefa3a11a100136f102d5ee26404cb2ec5192ce43a /src/core/chunks/reducer.ts
parent53686ae6446c5c59928244f65c08150fb564b375 (diff)
downloaddispatch-web-75680eac7071c6968eaa5470686ad102a82103db.tar.gz
dispatch-web-75680eac7071c6968eaa5470686ad102a82103db.zip
feat(chunks): split live thinking at reasoning-end block boundaries
Diffstat (limited to 'src/core/chunks/reducer.ts')
-rw-r--r--src/core/chunks/reducer.ts27
1 files changed, 27 insertions, 0 deletions
diff --git a/src/core/chunks/reducer.ts b/src/core/chunks/reducer.ts
index 35bb1bb..71df4dd 100644
--- a/src/core/chunks/reducer.ts
+++ b/src/core/chunks/reducer.ts
@@ -284,6 +284,8 @@ export function applyHistory(
* optimistic echo) so a watcher renders it mid-turn.
* - `text-delta` extends the current accumulating TextChunk (or starts one).
* - `reasoning-delta` extends the current accumulating ThinkingChunk (or starts one).
+ * - `reasoning-end` closes the open ThinkingChunk as its own chunk; with none open
+ * (a redacted/empty block) it appends an empty thinking chunk, mirroring history.
* - `tool-call` / `tool-result` / `error` finalize any accumulating chunk and
* add a new provisional chunk.
* - `steering` appends a user bubble mid-turn (drained from the message queue
@@ -415,6 +417,30 @@ function reduceEvent(state: TranscriptState, event: AgentEvent): TranscriptState
};
}
+ case "reasoning-end": {
+ const acc = state.accumulating;
+ // Close the open thinking block of this turn: flush it as its own provisional chunk.
+ if (acc !== null && acc.kind === "thinking" && acc.turnId === event.turnId) {
+ return {
+ ...state,
+ provisional: flushAccumulating(state.provisional, acc),
+ accumulating: null,
+ generating: true,
+ };
+ }
+ // No open thinking (a redacted/empty block with no deltas): history persists an
+ // EMPTY thinking chunk for it, so mirror that 1:1.
+ return {
+ ...state,
+ provisional: [
+ ...flushAccumulating(state.provisional, acc),
+ { role: "assistant", chunk: { type: "thinking", text: "" }, turnId: event.turnId },
+ ],
+ accumulating: null,
+ generating: true,
+ };
+ }
+
case "tool-call": {
const provisional = flushAccumulating(state.provisional, state.accumulating);
const chunk: Chunk = {
@@ -568,6 +594,7 @@ const RETRY_CLEARING_EVENTS: ReadonlySet<AgentEvent["type"]> = new Set([
"turn-start",
"text-delta",
"reasoning-delta",
+ "reasoning-end",
"tool-call",
"tool-result",
"error",