mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
perf: trim hot path allocations
This commit is contained in:
@@ -409,7 +409,7 @@ export function chunkMarkdown(
|
||||
}
|
||||
}
|
||||
current = kept;
|
||||
currentChars = kept.reduce((sum, entry) => sum + estimateStringChars(entry.line) + 1, 0);
|
||||
currentChars = acc;
|
||||
};
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
|
||||
@@ -83,6 +83,13 @@ describe("detectImageReferences", () => {
|
||||
expect(refs.some((r) => r.type === "path")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not leak parser state between calls", () => {
|
||||
expectSingleImageReference("[media attached: /tmp/first.png (image/png)]");
|
||||
expectSingleImageReference("[Image: source: /tmp/second.jpg]");
|
||||
expectSingleImageReference("See file:///tmp/third.webp");
|
||||
expectSingleImageReference("See ./fourth.jpeg");
|
||||
});
|
||||
|
||||
it("handles various image extensions", () => {
|
||||
const extensions = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "tiff", "heic"];
|
||||
for (const ext of extensions) {
|
||||
|
||||
@@ -41,6 +41,11 @@ const MESSAGE_IMAGE_REGEX_SOURCE =
|
||||
const FILE_URL_REGEX_SOURCE = "file://[^\\s<>\"'`\\]]+\\.(?:" + IMAGE_EXTENSION_PATTERN + ")";
|
||||
const PATH_REGEX_SOURCE =
|
||||
"(?:^|\\s|[\"'`(])((\\.\\.?/|[~/])[^\\s\"'`()\\[\\]]*\\.(?:" + IMAGE_EXTENSION_PATTERN + "))";
|
||||
const MEDIA_ATTACHED_PATTERN = /\[media attached(?:\s+\d+\/\d+)?:\s*([^\]]+)\]/gi;
|
||||
const MEDIA_ATTACHED_PATH_PATTERN = new RegExp(MEDIA_ATTACHED_PATH_REGEX_SOURCE, "i");
|
||||
const MESSAGE_IMAGE_PATTERN = new RegExp(MESSAGE_IMAGE_REGEX_SOURCE, "gi");
|
||||
const FILE_URL_PATTERN = new RegExp(FILE_URL_REGEX_SOURCE, "gi");
|
||||
const PATH_PATTERN = new RegExp(PATH_REGEX_SOURCE, "gi");
|
||||
|
||||
/**
|
||||
* Matches the opaque media URI written by the Gateway's claim-check offload:
|
||||
@@ -251,13 +256,12 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// Pattern for [media attached: path (type) | url] or [media attached N/M: path (type) | url] format
|
||||
// Each bracket = ONE file. The | separates path from URL, not multiple files.
|
||||
// Multi-file format uses separate brackets on separate lines.
|
||||
const mediaAttachedPattern = /\[media attached(?:\s+\d+\/\d+)?:\s*([^\]]+)\]/gi;
|
||||
const mediaAttachedPathPattern = new RegExp(MEDIA_ATTACHED_PATH_REGEX_SOURCE, "i");
|
||||
const messageImagePattern = new RegExp(MESSAGE_IMAGE_REGEX_SOURCE, "gi");
|
||||
const fileUrlPattern = new RegExp(FILE_URL_REGEX_SOURCE, "gi");
|
||||
const pathPattern = new RegExp(PATH_REGEX_SOURCE, "gi");
|
||||
MEDIA_ATTACHED_PATTERN.lastIndex = 0;
|
||||
MESSAGE_IMAGE_PATTERN.lastIndex = 0;
|
||||
FILE_URL_PATTERN.lastIndex = 0;
|
||||
PATH_PATTERN.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = mediaAttachedPattern.exec(prompt)) !== null) {
|
||||
while ((match = MEDIA_ATTACHED_PATTERN.exec(prompt)) !== null) {
|
||||
const content = match[1];
|
||||
|
||||
// Skip "[media attached: N files]" header lines
|
||||
@@ -283,14 +287,14 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// Format is: path (type) | url OR just: path (type)
|
||||
// Path may contain spaces (e.g., "ChatGPT Image Apr 21.png")
|
||||
// Use non-greedy .+? to stop at first image extension
|
||||
const pathMatch = content.match(mediaAttachedPathPattern);
|
||||
const pathMatch = content.match(MEDIA_ATTACHED_PATH_PATTERN);
|
||||
if (pathMatch?.[1]) {
|
||||
addPathRef(pathMatch[1].trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern for [Image: source: /path/...] format from messaging systems
|
||||
while ((match = messageImagePattern.exec(prompt)) !== null) {
|
||||
while ((match = MESSAGE_IMAGE_PATTERN.exec(prompt)) !== null) {
|
||||
const raw = match[1]?.trim();
|
||||
if (raw) {
|
||||
addPathRef(raw);
|
||||
@@ -300,7 +304,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
|
||||
|
||||
// Pattern for file:// URLs - treat as paths since loadWebMedia handles them
|
||||
while ((match = fileUrlPattern.exec(prompt)) !== null) {
|
||||
while ((match = FILE_URL_PATTERN.exec(prompt)) !== null) {
|
||||
const raw = match[0];
|
||||
const dedupeKey = normalizeRefForDedupe(raw);
|
||||
if (seen.has(dedupeKey)) {
|
||||
@@ -322,7 +326,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// - ./relative/path.ext
|
||||
// - ../parent/path.ext
|
||||
// - ~/home/path.ext
|
||||
while ((match = pathPattern.exec(prompt)) !== null) {
|
||||
while ((match = PATH_PATTERN.exec(prompt)) !== null) {
|
||||
// Use capture group 1 (the path without delimiter prefix); skip if undefined
|
||||
if (match[1]) {
|
||||
addPathRef(match[1]);
|
||||
|
||||
@@ -438,19 +438,18 @@ export function resolveFreshestSessionStoreMatchFromStoreKeys(
|
||||
store: Record<string, SessionEntry>,
|
||||
storeKeys: string[],
|
||||
): { key: string; entry: SessionEntry } | undefined {
|
||||
const matches = storeKeys
|
||||
.map((key) => {
|
||||
const entry = store[key];
|
||||
return entry ? { key, entry } : undefined;
|
||||
})
|
||||
.filter((match): match is { key: string; entry: SessionEntry } => match !== undefined);
|
||||
if (matches.length === 0) {
|
||||
return undefined;
|
||||
let freshest: { key: string; entry: SessionEntry } | undefined;
|
||||
for (const key of storeKeys) {
|
||||
const entry = store[key];
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
const match = { key, entry };
|
||||
if (!freshest || (match.entry.updatedAt ?? 0) > (freshest.entry.updatedAt ?? 0)) {
|
||||
freshest = match;
|
||||
}
|
||||
}
|
||||
if (matches.length === 1) {
|
||||
return matches[0];
|
||||
}
|
||||
return [...matches].toSorted((a, b) => (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0))[0];
|
||||
return freshest;
|
||||
}
|
||||
|
||||
export function resolveFreshestSessionEntryFromStoreKeys(
|
||||
@@ -484,9 +483,13 @@ function findFreshestStoreMatch(
|
||||
if (matches.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return [...matches.values()].toSorted(
|
||||
(a, b) => (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0),
|
||||
)[0];
|
||||
let freshest: { entry: SessionEntry; key: string } | undefined;
|
||||
for (const match of matches.values()) {
|
||||
if (!freshest || (match.entry.updatedAt ?? 0) > (freshest.entry.updatedAt ?? 0)) {
|
||||
freshest = match;
|
||||
}
|
||||
}
|
||||
return freshest;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -388,7 +388,7 @@ export function chunkMarkdown(
|
||||
}
|
||||
}
|
||||
current = kept;
|
||||
currentChars = kept.reduce((sum, entry) => sum + estimateStringChars(entry.line) + 1, 0);
|
||||
currentChars = acc;
|
||||
};
|
||||
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
|
||||
Reference in New Issue
Block a user