fix: hide system-reminder blocks in backfill + literal tag rendering (#935)

This commit is contained in:
Charles Packer
2026-02-12 14:27:49 -08:00
committed by GitHub
parent 28e67dadae
commit fa783fd2c8
4 changed files with 120 additions and 69 deletions

View File

@@ -49,7 +49,7 @@ const COMPACT_PAD = 1;
* System-reminder blocks are identified by <system-reminder>...</system-reminder> tags.
* Returns array of { text, isSystemReminder } objects in order.
*/
function splitSystemReminderBlocks(
export function splitSystemReminderBlocks(
text: string,
): Array<{ text: string; isSystemReminder: boolean }> {
const blocks: Array<{ text: string; isSystemReminder: boolean }> = [];
@@ -69,6 +69,17 @@ function splitSystemReminderBlocks(
break;
}
// Find the closing tag
const closeIdx = remaining.indexOf(tagClose, openIdx);
if (closeIdx === -1) {
// Malformed/incomplete tag - treat the whole remainder as literal user text.
const literal = remaining.trim();
if (literal) {
blocks.push({ text: literal, isSystemReminder: false });
}
break;
}
// Content before the tag is user content
if (openIdx > 0) {
const before = remaining.slice(0, openIdx).trim();
@@ -77,17 +88,6 @@ function splitSystemReminderBlocks(
}
}
// Find the closing tag
const closeIdx = remaining.indexOf(tagClose, openIdx);
if (closeIdx === -1) {
// Malformed - no closing tag, treat rest as system-reminder
blocks.push({
text: remaining.slice(openIdx).trim(),
isSystemReminder: true,
});
break;
}
// Extract the full system-reminder block (including tags)
const sysBlock = remaining.slice(openIdx, closeIdx + tagClose.length);
blocks.push({ text: sysBlock, isSystemReminder: true });

View File

@@ -41,41 +41,16 @@ function normalizeLineEndings(s: string): string {
return s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}
/**
* Truncate system-reminder content while preserving opening/closing tags.
* Removes the middle content and replaces with [...] to keep the message compact
* but with proper tag structure.
*/
function truncateSystemReminder(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
const openIdx = text.indexOf(SYSTEM_REMINDER_OPEN);
const closeIdx = text.lastIndexOf(SYSTEM_REMINDER_CLOSE);
if (openIdx === -1 || closeIdx === -1 || closeIdx <= openIdx) {
// Malformed, just use regular clip
return clip(text, maxLength);
}
const openEnd = openIdx + SYSTEM_REMINDER_OPEN.length;
const ellipsis = "\n...\n";
// Calculate available space for content (split between start and end)
const overhead =
SYSTEM_REMINDER_OPEN.length +
SYSTEM_REMINDER_CLOSE.length +
ellipsis.length;
const availableContent = maxLength - overhead;
if (availableContent <= 0) {
// Not enough space, just show tags with ellipsis
return `${SYSTEM_REMINDER_OPEN}${ellipsis}${SYSTEM_REMINDER_CLOSE}`;
}
const halfContent = Math.floor(availableContent / 2);
const contentStart = text.slice(openEnd, openEnd + halfContent);
const contentEnd = text.slice(closeIdx - halfContent, closeIdx);
return `${SYSTEM_REMINDER_OPEN}${contentStart}${ellipsis}${contentEnd}${SYSTEM_REMINDER_CLOSE}`;
function removeSystemReminderBlocks(text: string): string {
return text
.replace(
new RegExp(
`${SYSTEM_REMINDER_OPEN}[\\s\\S]*?${SYSTEM_REMINDER_CLOSE}`,
"g",
),
"",
)
.trim();
}
/**
@@ -119,25 +94,16 @@ function renderAssistantContentParts(
return out;
}
/**
* Check if text is purely a system-reminder block (no user content before/after).
*/
function isOnlySystemReminder(text: string): boolean {
const trimmed = text.trim();
return (
trimmed.startsWith(SYSTEM_REMINDER_OPEN) &&
trimmed.endsWith(SYSTEM_REMINDER_CLOSE)
);
}
function renderUserContentParts(
parts: string | LettaUserMessageContentUnion[],
): string {
// UserContent can be a string or an array of text OR image parts
// Pure system-reminder parts are truncated (middle) to preserve tags
// Mixed content or user text uses simple end truncation
// UserContent can be a string or an array of text OR image parts.
// Backfill should hide system-reminder blocks entirely.
// Parts are joined with newlines so each appears as a separate line
if (typeof parts === "string") return parts;
if (typeof parts === "string") {
const normalized = normalizeLineEndings(parts);
return clip(removeSystemReminderBlocks(normalized), CLIP_CHAR_LIMIT_TEXT);
}
const rendered: string[] = [];
for (const p of parts) {
@@ -145,13 +111,9 @@ function renderUserContentParts(
const text = p.text || "";
// Normalize line endings (\r\n and \r -> \n) to prevent terminal garbling
const normalized = normalizeLineEndings(text);
if (isOnlySystemReminder(normalized)) {
// Pure system-reminder: truncate middle to preserve tags
rendered.push(truncateSystemReminder(normalized, CLIP_CHAR_LIMIT_TEXT));
} else {
// User content or mixed: simple end truncation
rendered.push(clip(normalized, CLIP_CHAR_LIMIT_TEXT));
}
const withoutSystemReminders = removeSystemReminderBlocks(normalized);
if (!withoutSystemReminders) continue;
rendered.push(clip(withoutSystemReminders, CLIP_CHAR_LIMIT_TEXT));
} else if (p.type === "image") {
rendered.push("[Image]");
}