mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-04 07:33:18 +00:00
* fix(app): cap oversized assistant messages A single assistant message can expand into enough rendered Markdown nodes to crash the renderer. Limit the Markdown input while preserving the complete timeline value and report its full byte size. * fix(app): bound oversized stream processing Cap the render input before paced reveal and defer full-message byte counting until completion. Reuse the existing grapheme boundary policy so capped text never ends in a partial glyph. * fix(app): preserve capped text without segmentation Hermes lacks Intl.Segmenter, while the shared reveal boundary intentionally returns zero without it. Keep that contract and fall back locally to a surrogate-safe 32K prefix.
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT,
|
|
capAssistantMessageForRender,
|
|
getUtf8ByteLength,
|
|
} from "./assistant-message-render-limit";
|
|
|
|
describe("assistant message render limit", () => {
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
vi.resetModules();
|
|
});
|
|
|
|
it("leaves messages at the limit unchanged", () => {
|
|
const message = "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT);
|
|
|
|
expect(capAssistantMessageForRender(message)).toEqual({ text: message, capped: false });
|
|
});
|
|
|
|
it("caps oversized messages before markdown rendering", () => {
|
|
const message = "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT + 1);
|
|
|
|
expect(capAssistantMessageForRender(message)).toEqual({
|
|
text: "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT),
|
|
capped: true,
|
|
});
|
|
});
|
|
|
|
it("does not split a grapheme cluster at the boundary", () => {
|
|
const prefix = "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT - 1);
|
|
|
|
expect(capAssistantMessageForRender(`${prefix}étail`)).toEqual({
|
|
text: prefix,
|
|
capped: true,
|
|
});
|
|
});
|
|
|
|
it("counts the complete message size in UTF-8 bytes", () => {
|
|
expect(getUtf8ByteLength("aé😀")).toBe(7);
|
|
});
|
|
|
|
it("keeps a bounded prefix when grapheme segmentation is unavailable", async () => {
|
|
vi.resetModules();
|
|
vi.stubGlobal("Intl", { ...Intl, Segmenter: undefined });
|
|
const { capAssistantMessageForRender: capWithoutSegmenter } =
|
|
await import("./assistant-message-render-limit");
|
|
const message = "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT + 1);
|
|
|
|
expect(capWithoutSegmenter(message)).toEqual({
|
|
text: "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT),
|
|
capped: true,
|
|
});
|
|
});
|
|
});
|