mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation (#101517)
* fix(session-cost-usage): keep emoji / surrogate pairs intact during content truncation .slice(0, maxLen) counts UTF-16 code units, so an emoji straddling the cut point produces a lone surrogate that renders as � in usage cost CLI output. Use [...str] to count full code points instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(usage): cover UTF-16-safe log truncation --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -2993,6 +2993,25 @@ example
|
||||
expect(logs?.[0]?.content).toBe("hello there");
|
||||
});
|
||||
|
||||
it("does not split surrogate pairs when truncating session log content", async () => {
|
||||
const root = await makeSessionCostRoot("logs-utf16");
|
||||
const sessionFile = path.join(root, "session.jsonl");
|
||||
const content = "x".repeat(1999) + "🚀tail";
|
||||
await fs.writeFile(
|
||||
sessionFile,
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
timestamp: "2026-02-21T17:47:00.000Z",
|
||||
message: { role: "assistant", content },
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const logs = await loadSessionLogs({ sessionFile });
|
||||
|
||||
expect(logs?.[0]?.content).toBe(`${"x".repeat(1999)}…`);
|
||||
});
|
||||
|
||||
it("normalizes malformed log timestamps with the transcript timestamp rules", async () => {
|
||||
const root = await makeSessionCostRoot("logs-malformed-timestamp");
|
||||
const sessionsDir = path.join(root, "agents", "main", "sessions");
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
|
||||
import type { NormalizedUsage, UsageLike } from "../agents/usage.js";
|
||||
import { normalizeUsage } from "../agents/usage.js";
|
||||
import { stripInboundMetadata } from "../auto-reply/reply/strip-inbound-meta.js";
|
||||
@@ -2749,10 +2750,10 @@ export async function loadSessionLogs(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Truncate very long content
|
||||
// Truncate very long content.
|
||||
const maxLen = 2000;
|
||||
if (content.length > maxLen) {
|
||||
content = content.slice(0, maxLen) + "…";
|
||||
content = truncateUtf16Safe(content, maxLen) + "…";
|
||||
}
|
||||
|
||||
// Get timestamp
|
||||
|
||||
Reference in New Issue
Block a user