mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:50:46 +00:00
fix(gateway): preserve oversized transcript tree leaves
This commit is contained in:
@@ -1704,6 +1704,38 @@ describe("oversized transcript line guards", () => {
|
||||
expect(serialized).toContain("after oversized");
|
||||
});
|
||||
|
||||
test("readRecentSessionMessagesAsync keeps oversized active-tree leaves", async () => {
|
||||
const sessionId = "test-oversized-tree-tail";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const oversizedContent = "z".repeat(300 * 1024);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 3, id: sessionId }),
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
id: "root",
|
||||
parentId: null,
|
||||
message: { role: "user", content: "root" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
id: "oversized-leaf",
|
||||
parentId: "root",
|
||||
message: { role: "assistant", content: oversizedContent },
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, `${lines.join("\n")}\n`, "utf-8");
|
||||
|
||||
const out = await readRecentSessionMessagesAsync(sessionId, storePath, undefined, {
|
||||
maxMessages: 10,
|
||||
});
|
||||
|
||||
const serialized = JSON.stringify(out);
|
||||
expect(serialized).toContain("root");
|
||||
expect(serialized).toContain("oversized-leaf");
|
||||
expect(serialized).not.toContain(oversizedContent);
|
||||
expect(serialized).toContain("[chat.history omitted: message too large]");
|
||||
});
|
||||
|
||||
test("readRecentSessionUsageFromTranscriptAsync skips oversized lines", async () => {
|
||||
const sessionId = "test-oversized-usage";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
|
||||
@@ -267,22 +267,57 @@ async function readRecentTranscriptTailLinesAsync(
|
||||
}
|
||||
|
||||
const MAX_TRANSCRIPT_PARSE_LINE_BYTES = 256 * 1024;
|
||||
const OVERSIZED_TRANSCRIPT_METADATA_PREFIX_CHARS = 64 * 1024;
|
||||
const TRANSCRIPT_OVERSIZED_MESSAGE_PLACEHOLDER = "[chat.history omitted: message too large]";
|
||||
|
||||
function isOversizedTranscriptLine(line: string): boolean {
|
||||
return Buffer.byteLength(line, "utf8") > MAX_TRANSCRIPT_PARSE_LINE_BYTES;
|
||||
}
|
||||
|
||||
function buildOversizedTranscriptRecord(): TailTranscriptRecord {
|
||||
return {
|
||||
record: {
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: TRANSCRIPT_OVERSIZED_MESSAGE_PLACEHOLDER }],
|
||||
__openclaw: { truncated: true, reason: "oversized" },
|
||||
},
|
||||
function extractJsonStringFieldPrefix(prefix: string, field: string): string | undefined {
|
||||
const match = new RegExp(`"${field}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`).exec(prefix);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const decoded = JSON.parse(`"${match[1]}"`) as unknown;
|
||||
return normalizeTailEntryString(decoded);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function extractJsonNullableStringFieldPrefix(
|
||||
prefix: string,
|
||||
field: string,
|
||||
): string | null | undefined {
|
||||
if (new RegExp(`"${field}"\\s*:\\s*null`).test(prefix)) {
|
||||
return null;
|
||||
}
|
||||
return extractJsonStringFieldPrefix(prefix, field);
|
||||
}
|
||||
|
||||
function buildOversizedTranscriptRecord(line: string): TailTranscriptRecord {
|
||||
const prefix = line.slice(0, OVERSIZED_TRANSCRIPT_METADATA_PREFIX_CHARS);
|
||||
const id = extractJsonStringFieldPrefix(prefix, "id");
|
||||
const parentId = extractJsonNullableStringFieldPrefix(prefix, "parentId");
|
||||
const type = extractJsonStringFieldPrefix(prefix, "type");
|
||||
const role = extractJsonStringFieldPrefix(prefix, "role") ?? "assistant";
|
||||
const record: Record<string, unknown> = {
|
||||
...(type ? { type } : {}),
|
||||
...(id ? { id } : {}),
|
||||
...(parentId !== undefined ? { parentId } : {}),
|
||||
message: {
|
||||
role,
|
||||
content: [{ type: "text", text: TRANSCRIPT_OVERSIZED_MESSAGE_PLACEHOLDER }],
|
||||
__openclaw: { truncated: true, reason: "oversized" },
|
||||
},
|
||||
};
|
||||
return {
|
||||
...(id ? { id } : {}),
|
||||
...(parentId !== undefined ? { parentId } : {}),
|
||||
record,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTailEntryString(value: unknown): string | undefined {
|
||||
@@ -291,7 +326,7 @@ function normalizeTailEntryString(value: unknown): string | undefined {
|
||||
|
||||
function parseTailTranscriptRecord(line: string): TailTranscriptRecord | null {
|
||||
if (isOversizedTranscriptLine(line)) {
|
||||
return buildOversizedTranscriptRecord();
|
||||
return buildOversizedTranscriptRecord(line);
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(line) as unknown;
|
||||
|
||||
Reference in New Issue
Block a user