Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7363,4 +7363,63 @@ describe("splitBufferedAssistantText", () => {
rest: "```\n- one\n- two\n",
});
});

it("holds headings until the following block is ready", () => {
expect(splitBufferedAssistantText("intro\n\n## Setup\n\nInstall it")).toEqual({
ready: "intro\n\n",
rest: "## Setup\n\nInstall it",
});
expect(
splitBufferedAssistantText("intro\n\n# Plan\n\n## Setup\n\nInstall it.\n\nNext"),
).toEqual({
ready: "intro\n\n# Plan\n\n## Setup\n\nInstall it.\n\n",
rest: "Next",
});
expect(splitBufferedAssistantText("para\n## Setup\n\nInstall")).toEqual({
ready: "para\n",
rest: "## Setup\n\nInstall",
});
expect(splitBufferedAssistantText("intro\n\n## ")).toEqual({
ready: "intro\n\n",
rest: "## ",
});
});

it("holds standalone bold titles, but not bold text within a paragraph", () => {
expect(splitBufferedAssistantText("**Risk by area:**\n\n| a |\n|---|\n")).toEqual({
ready: "",
rest: "**Risk by area:**\n\n| a |\n|---|\n",
});
expect(splitBufferedAssistantText("**Use *npm* now**\n\nInstall it")).toEqual({
ready: "",
rest: "**Use *npm* now**\n\nInstall it",
});
expect(splitBufferedAssistantText("**Note:** read this.\n\nNext")).toEqual({
ready: "**Note:** read this.\n\n",
rest: "Next",
});
expect(splitBufferedAssistantText("para\n**Setup**\n\nInstall")).toEqual({
ready: "",
rest: "para\n**Setup**\n\nInstall",
});
});

it("delivers a title with its first list item or completed code block", () => {
expect(splitBufferedAssistantText("## Steps\n\n- one\n- tw")).toEqual({
ready: "## Steps\n\n- one\n",
rest: "- tw",
});
expect(splitBufferedAssistantText("## Code\n\n```ts\na\n\nb\n")).toEqual({
ready: "",
rest: "## Code\n\n```ts\na\n\nb\n",
});
expect(splitBufferedAssistantText("## Code\n\n```ts\na\n```\nafter")).toEqual({
ready: "## Code\n\n```ts\na\n```\n",
rest: "after",
});
expect(splitBufferedAssistantText("```md\n## Inside\n\ntext\n")).toEqual({
ready: "",
rest: "```md\n## Inside\n\ntext\n",
});
});
});
22 changes: 20 additions & 2 deletions apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,18 +275,28 @@ const MARKDOWN_FENCE_PATTERN = /^( *)(`{3,}|~{3,})/;
const BLANK_LINE_PATTERN = /^[ \t]*$/;
// A complete list marker lets the previous item land before the next is done.
const LIST_ITEM_START_PATTERN = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]/;
// Models also use a line of bold text as a section title.
const SECTION_TITLE_PATTERN = /^ {0,3}(?:#{1,6}(?:[ \t]|$)|\*\*(?:[^*]|\*(?!\*))+\*\*:?$)/;
// A top-level heading ends the paragraph above it even without a blank line.
const TOP_LEVEL_HEADING_PATTERN = /^#{1,6}(?:[ \t]|$)/;

/** Return text whose Markdown shape is complete, leaving the active block buffered. */
export function splitBufferedAssistantText(text: string): { ready: string; rest: string } {
let openFence: { marker: string; indent: number } | null = null;
let boundary = -1;
let lineStart = 0;
let titleAwaitingContent = false;
for (;;) {
const newline = text.indexOf("\n", lineStart);
const line = text
.slice(lineStart, newline === -1 ? text.length : newline)
.replace(/[ \t\r]+$/, "");
if (openFence === null && lineStart > 0 && LIST_ITEM_START_PATTERN.test(line)) {
if (
openFence === null &&
lineStart > 0 &&
!titleAwaitingContent &&
LIST_ITEM_START_PATTERN.test(line)
) {
boundary = lineStart;
}
if (newline === -1) break;
Expand All @@ -296,6 +306,7 @@ export function splitBufferedAssistantText(text: string): { ready: string; rest:
const marker = fenceMatch[2]!;
if (openFence === null) {
openFence = { marker, indent };
titleAwaitingContent = false;
} else if (
marker[0] === openFence.marker[0] &&
marker.length >= openFence.marker.length &&
Expand All @@ -306,7 +317,14 @@ export function splitBufferedAssistantText(text: string): { ready: string; rest:
boundary = newline + 1;
}
} else if (openFence === null && BLANK_LINE_PATTERN.test(line) && lineStart > 0) {
boundary = newline + 1;
if (!titleAwaitingContent) {
boundary = newline + 1;
}
} else if (openFence === null) {
if (lineStart > 0 && !titleAwaitingContent && TOP_LEVEL_HEADING_PATTERN.test(line)) {
boundary = lineStart;
}
titleAwaitingContent = SECTION_TITLE_PATTERN.test(line);
}
lineStart = newline + 1;
}
Expand Down
Loading