refactor: streamline TUI stream assembly updates

This commit is contained in:
Peter Steinberger
2026-01-20 08:45:07 +00:00
parent c17c7b4e24
commit 32a668e4d9
3 changed files with 48 additions and 20 deletions

View File

@@ -87,4 +87,14 @@ describe("extractContentFromMessage", () => {
expect(text).toBe("hello"); expect(text).toBe("hello");
}); });
it("renders error text when stopReason is error and content is not an array", () => {
const text = extractContentFromMessage({
role: "assistant",
stopReason: "error",
errorMessage: '429 {"error":{"message":"rate limit"}}',
});
expect(text).toContain("HTTP 429");
});
}); });

View File

@@ -65,4 +65,29 @@ describe("TuiStreamAssembler", () => {
expect(finalText).toBe("Streamed"); expect(finalText).toBe("Streamed");
}); });
it("returns null when delta text is unchanged", () => {
const assembler = new TuiStreamAssembler();
const first = assembler.ingestDelta(
"run-4",
{
role: "assistant",
content: [{ type: "text", text: "Repeat" }],
},
false,
);
expect(first).toBe("Repeat");
const second = assembler.ingestDelta(
"run-4",
{
role: "assistant",
content: [{ type: "text", text: "Repeat" }],
},
false,
);
expect(second).toBeNull();
});
}); });

View File

@@ -27,10 +27,9 @@ export class TuiStreamAssembler {
return state; return state;
} }
ingestDelta(runId: string, message: unknown, showThinking: boolean): string | null { private updateRunState(state: RunStreamState, message: unknown, showThinking: boolean) {
const thinkingText = extractThinkingFromMessage(message); const thinkingText = extractThinkingFromMessage(message);
const contentText = extractContentFromMessage(message); const contentText = extractContentFromMessage(message);
const state = this.getOrCreateRun(runId);
if (thinkingText) { if (thinkingText) {
state.thinkingText = thinkingText; state.thinkingText = thinkingText;
@@ -45,29 +44,23 @@ export class TuiStreamAssembler {
showThinking, showThinking,
}); });
if (!displayText || displayText === state.displayText) return null;
state.displayText = displayText; state.displayText = displayText;
return displayText; }
ingestDelta(runId: string, message: unknown, showThinking: boolean): string | null {
const state = this.getOrCreateRun(runId);
const previousDisplayText = state.displayText;
this.updateRunState(state, message, showThinking);
if (!state.displayText || state.displayText === previousDisplayText) return null;
return state.displayText;
} }
finalize(runId: string, message: unknown, showThinking: boolean): string { finalize(runId: string, message: unknown, showThinking: boolean): string {
const state = this.getOrCreateRun(runId); const state = this.getOrCreateRun(runId);
const thinkingText = extractThinkingFromMessage(message); this.updateRunState(state, message, showThinking);
const contentText = extractContentFromMessage(message); const finalComposed = state.displayText;
if (thinkingText) {
state.thinkingText = thinkingText;
}
if (contentText) {
state.contentText = contentText;
}
const finalComposed = composeThinkingAndContent({
thinkingText: state.thinkingText,
contentText: state.contentText,
showThinking,
});
const finalText = resolveFinalAssistantText({ const finalText = resolveFinalAssistantText({
finalText: finalComposed, finalText: finalComposed,
streamedText: state.displayText, streamedText: state.displayText,