perf(gateway): avoid heavy chat imports in history tests

This commit is contained in:
Vincent Koc
2026-06-04 01:12:54 +02:00
parent 0416117168
commit 25f3d2d714
5 changed files with 273 additions and 273 deletions

View File

@@ -5,6 +5,7 @@ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coe
import { OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE } from "../agents/internal-runtime-context.js";
import { isHeartbeatOkResponse, isHeartbeatUserMessage } from "../auto-reply/heartbeat-filter.js";
import { HEARTBEAT_PROMPT } from "../auto-reply/heartbeat.js";
import { extractCanvasFromText } from "../chat/canvas-render.js";
import {
INTER_SESSION_PROMPT_PREFIX_BASE,
normalizeInputProvenance,
@@ -69,6 +70,178 @@ export function isToolHistoryBlockType(type: unknown): boolean {
);
}
function extractChatHistoryBlockText(message: unknown): string | undefined {
if (!message || typeof message !== "object") {
return undefined;
}
const entry = message as Record<string, unknown>;
if (typeof entry.content === "string") {
return entry.content;
}
if (typeof entry.text === "string") {
return entry.text;
}
if (!Array.isArray(entry.content)) {
return undefined;
}
const textParts = entry.content
.map((block) => {
if (!block || typeof block !== "object") {
return undefined;
}
const typed = block as { text?: unknown };
return typeof typed.text === "string" ? typed.text : undefined;
})
.filter((value): value is string => typeof value === "string");
return textParts.length > 0 ? textParts.join("\n") : undefined;
}
function appendCanvasBlockToAssistantHistoryMessage(params: {
message: unknown;
preview: ReturnType<typeof extractCanvasFromText>;
rawText: string | null;
}): unknown {
const preview = params.preview;
if (!preview || !params.message || typeof params.message !== "object") {
return params.message;
}
const entry = params.message as Record<string, unknown>;
const baseContent = Array.isArray(entry.content)
? [...entry.content]
: typeof entry.content === "string"
? [{ type: "text", text: entry.content }]
: typeof entry.text === "string"
? [{ type: "text", text: entry.text }]
: [];
const alreadyPresent = baseContent.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
const typed = block as { type?: unknown; preview?: unknown };
return (
typed.type === "canvas" &&
typed.preview &&
typeof typed.preview === "object" &&
(((typed.preview as { viewId?: unknown }).viewId &&
(typed.preview as { viewId?: unknown }).viewId === preview.viewId) ||
((typed.preview as { url?: unknown }).url &&
(typed.preview as { url?: unknown }).url === preview.url))
);
});
if (!alreadyPresent) {
baseContent.push({
type: "canvas",
preview,
rawText: params.rawText,
});
}
return {
...entry,
content: baseContent,
};
}
function messageContainsToolHistoryContent(message: unknown): boolean {
if (!message || typeof message !== "object") {
return false;
}
const entry = message as Record<string, unknown>;
if (
typeof entry.toolCallId === "string" ||
typeof entry.tool_call_id === "string" ||
typeof entry.toolName === "string" ||
typeof entry.tool_name === "string"
) {
return true;
}
if (!Array.isArray(entry.content)) {
return false;
}
return entry.content.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
return isToolHistoryBlockType((block as { type?: unknown }).type);
});
}
export function augmentChatHistoryWithCanvasBlocks(messages: unknown[]): unknown[] {
if (messages.length === 0) {
return messages;
}
const next = [...messages];
let changed = false;
let lastAssistantIndex = -1;
let lastRenderableAssistantIndex = -1;
const pending: Array<{
preview: NonNullable<ReturnType<typeof extractCanvasFromText>>;
rawText: string | null;
}> = [];
for (let index = 0; index < next.length; index++) {
const message = next[index];
if (!message || typeof message !== "object") {
continue;
}
const entry = message as Record<string, unknown>;
const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
if (role === "assistant") {
lastAssistantIndex = index;
if (!messageContainsToolHistoryContent(entry)) {
lastRenderableAssistantIndex = index;
if (pending.length > 0) {
let target = next[index];
for (const item of pending) {
target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText,
});
}
next[index] = target;
pending.length = 0;
changed = true;
}
}
continue;
}
if (!messageContainsToolHistoryContent(entry)) {
continue;
}
const toolName =
typeof entry.toolName === "string"
? entry.toolName
: typeof entry.tool_name === "string"
? entry.tool_name
: undefined;
const text = extractChatHistoryBlockText(entry);
const preview = extractCanvasFromText(text, toolName);
if (!preview) {
continue;
}
pending.push({
preview,
rawText: text ?? null,
});
}
if (pending.length > 0) {
const targetIndex =
lastRenderableAssistantIndex >= 0 ? lastRenderableAssistantIndex : lastAssistantIndex;
if (targetIndex >= 0) {
let target = next[targetIndex];
for (const item of pending) {
target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText,
});
}
next[targetIndex] = target;
changed = true;
}
}
return changed ? next : messages;
}
function sanitizeChatHistoryContentBlock(
block: unknown,
opts?: { preserveExactToolPayload?: boolean; maxChars?: number },
@@ -1093,6 +1266,71 @@ function isSubagentAnnounceInterSessionUserMessage(message: Record<string, unkno
);
}
function readChatHistoryRecordTimestampMs(message: unknown): number | undefined {
const meta = readRecord(readRecord(message)?.["__openclaw"]);
const value = meta?.recordTimestampMs;
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
const timestamp = readRecord(message)?.timestamp;
return typeof timestamp === "number" && Number.isFinite(timestamp) ? timestamp : undefined;
}
function isSubagentAnnounceInterSessionUserChatHistoryMessage(message: unknown): boolean {
const record = readRecord(message);
if (!record || record.role !== "user") {
return false;
}
const provenance = normalizeInputProvenance(record.provenance);
if (provenance?.kind === "inter_session" && provenance.sourceTool === "subagent_announce") {
return true;
}
const text = extractChatHistoryBlockText(record);
return (
typeof text === "string" &&
text.includes(INTER_SESSION_PROMPT_PREFIX_BASE) &&
text.includes("sourceTool=subagent_announce")
);
}
function isChatHistoryAssistantMessage(message: unknown): boolean {
return readRecord(message)?.role === "assistant";
}
export function dropPreSessionStartAnnouncePairs(
messages: unknown[],
sessionStartedAt: number | undefined,
): unknown[] {
if (sessionStartedAt === undefined || messages.length === 0) {
return messages;
}
let changed = false;
const kept: unknown[] = [];
for (let i = 0; i < messages.length; i++) {
const current = messages[i];
if (isSubagentAnnounceInterSessionUserChatHistoryMessage(current)) {
const ts = readChatHistoryRecordTimestampMs(current);
if (typeof ts === "number" && ts < sessionStartedAt) {
const next = messages[i + 1];
const nextTs = readChatHistoryRecordTimestampMs(next);
if (
isChatHistoryAssistantMessage(next) &&
typeof nextTs === "number" &&
nextTs < sessionStartedAt
) {
// Skip only an assistant reply that is also pre-session-start; recent
// or timestampless assistants may be real fresh-session context.
i++;
}
changed = true;
continue;
}
}
kept.push(current);
}
return changed ? kept : messages;
}
function isSessionsSendInterSessionUserMessage(message: Record<string, unknown>): boolean {
if (message.role !== "user") {
return false;

View File

@@ -0,0 +1,20 @@
function stripDisallowedChatControlChars(message: string): string {
let output = "";
for (const char of message) {
const code = char.charCodeAt(0);
if (code === 9 || code === 10 || code === 13 || (code >= 32 && code !== 127)) {
output += char;
}
}
return output;
}
export function sanitizeChatSendMessageInput(
message: string,
): { ok: true; message: string } | { ok: false; error: string } {
const normalized = message.normalize("NFC");
if (normalized.includes("\u0000")) {
return { ok: false, error: "message must not contain null bytes" };
}
return { ok: true, message: stripDisallowedChatControlChars(normalized) };
}

View File

@@ -46,7 +46,6 @@ import { getReplyPayloadMetadata, type ReplyPayload } from "../../auto-reply/rep
import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.js";
import { stageSandboxMedia } from "../../auto-reply/reply/stage-sandbox-media.js";
import type { MsgContext, TemplateContext } from "../../auto-reply/templating.js";
import { extractCanvasFromText } from "../../chat/canvas-render.js";
import { resolveSessionFilePath } from "../../config/sessions.js";
import { resolveMirroredTranscriptText } from "../../config/sessions/transcript-mirror.js";
import { CURRENT_SESSION_VERSION } from "../../config/sessions/version.js";
@@ -76,11 +75,7 @@ import { createChannelMessageReplyPipeline } from "../../plugin-sdk/channel-outb
import type { ChannelRouteRef } from "../../plugin-sdk/channel-route.js";
import { isPluginOwnedSessionBindingRecord } from "../../plugins/conversation-binding.js";
import { normalizeAgentId, scopeLegacySessionKeyToAgent } from "../../routing/session-key.js";
import {
INTER_SESSION_PROMPT_PREFIX_BASE,
normalizeInputProvenance,
type InputProvenance,
} from "../../sessions/input-provenance.js";
import { normalizeInputProvenance, type InputProvenance } from "../../sessions/input-provenance.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { parseAgentSessionKey } from "../../sessions/session-key-utils.js";
import {
@@ -119,11 +114,13 @@ import {
UnsupportedAttachmentError,
} from "../chat-attachments.js";
import {
isToolHistoryBlockType,
augmentChatHistoryWithCanvasBlocks,
dropPreSessionStartAnnouncePairs,
projectChatDisplayMessage,
projectRecentChatDisplayMessages,
resolveEffectiveChatHistoryMaxChars,
} from "../chat-display-projection.js";
import { sanitizeChatSendMessageInput } from "../chat-input-sanitize.js";
import { stripEnvelopeFromMessage } from "../chat-sanitize.js";
import { augmentChatHistoryWithCliSessionImports } from "../cli-session-history.js";
import { isSuppressedControlReplyText } from "../control-reply-text.js";
@@ -453,10 +450,13 @@ async function buildWebchatAssistantMediaMessage(
}
export {
augmentChatHistoryWithCanvasBlocks,
DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS,
dropPreSessionStartAnnouncePairs,
resolveEffectiveChatHistoryMaxChars,
sanitizeChatHistoryMessages,
} from "../chat-display-projection.js";
export { sanitizeChatSendMessageInput } from "../chat-input-sanitize.js";
export const CHAT_HISTORY_MAX_SINGLE_MESSAGE_BYTES = 128 * 1024;
const CHAT_HISTORY_OVERSIZED_PLACEHOLDER = "[chat.history omitted: message too large]";
@@ -1075,27 +1075,6 @@ function explicitOriginTargetsPluginBinding(origin: ChatSendExplicitOrigin | und
return isPluginOwnedSessionBindingRecord(binding);
}
function stripDisallowedChatControlChars(message: string): string {
let output = "";
for (const char of message) {
const code = char.charCodeAt(0);
if (code === 9 || code === 10 || code === 13 || (code >= 32 && code !== 127)) {
output += char;
}
}
return output;
}
export function sanitizeChatSendMessageInput(
message: string,
): { ok: true; message: string } | { ok: false; error: string } {
const normalized = message.normalize("NFC");
if (normalized.includes("\u0000")) {
return { ok: false, error: "message must not contain null bytes" };
}
return { ok: true, message: stripDisallowedChatControlChars(normalized) };
}
function normalizeOptionalChatSystemReceipt(
value: unknown,
): { ok: true; receipt?: string } | { ok: false; error: string } {
@@ -1381,178 +1360,6 @@ function buildChatSendUserTurnMedia(savedMedia: SavedMedia[]): NonNullable<UserT
}));
}
function extractChatHistoryBlockText(message: unknown): string | undefined {
if (!message || typeof message !== "object") {
return undefined;
}
const entry = message as Record<string, unknown>;
if (typeof entry.content === "string") {
return entry.content;
}
if (typeof entry.text === "string") {
return entry.text;
}
if (!Array.isArray(entry.content)) {
return undefined;
}
const textParts = entry.content
.map((block) => {
if (!block || typeof block !== "object") {
return undefined;
}
const typed = block as { text?: unknown; type?: unknown };
return typeof typed.text === "string" ? typed.text : undefined;
})
.filter((value): value is string => typeof value === "string");
return textParts.length > 0 ? textParts.join("\n") : undefined;
}
function appendCanvasBlockToAssistantHistoryMessage(params: {
message: unknown;
preview: ReturnType<typeof extractCanvasFromText>;
rawText: string | null;
}): unknown {
const preview = params.preview;
if (!preview || !params.message || typeof params.message !== "object") {
return params.message;
}
const entry = params.message as Record<string, unknown>;
const baseContent = Array.isArray(entry.content)
? [...entry.content]
: typeof entry.content === "string"
? [{ type: "text", text: entry.content }]
: typeof entry.text === "string"
? [{ type: "text", text: entry.text }]
: [];
const alreadyPresent = baseContent.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
const typed = block as { type?: unknown; preview?: unknown };
return (
typed.type === "canvas" &&
typed.preview &&
typeof typed.preview === "object" &&
(((typed.preview as { viewId?: unknown }).viewId &&
(typed.preview as { viewId?: unknown }).viewId === preview.viewId) ||
((typed.preview as { url?: unknown }).url &&
(typed.preview as { url?: unknown }).url === preview.url))
);
});
if (!alreadyPresent) {
baseContent.push({
type: "canvas",
preview,
rawText: params.rawText,
});
}
return {
...entry,
content: baseContent,
};
}
function messageContainsToolHistoryContent(message: unknown): boolean {
if (!message || typeof message !== "object") {
return false;
}
const entry = message as Record<string, unknown>;
if (
typeof entry.toolCallId === "string" ||
typeof entry.tool_call_id === "string" ||
typeof entry.toolName === "string" ||
typeof entry.tool_name === "string"
) {
return true;
}
if (!Array.isArray(entry.content)) {
return false;
}
return entry.content.some((block) => {
if (!block || typeof block !== "object") {
return false;
}
return isToolHistoryBlockType((block as { type?: unknown }).type);
});
}
export function augmentChatHistoryWithCanvasBlocks(messages: unknown[]): unknown[] {
if (messages.length === 0) {
return messages;
}
const next = [...messages];
let changed = false;
let lastAssistantIndex = -1;
let lastRenderableAssistantIndex = -1;
const pending: Array<{
preview: NonNullable<ReturnType<typeof extractCanvasFromText>>;
rawText: string | null;
}> = [];
for (let index = 0; index < next.length; index++) {
const message = next[index];
if (!message || typeof message !== "object") {
continue;
}
const entry = message as Record<string, unknown>;
const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
if (role === "assistant") {
lastAssistantIndex = index;
if (!messageContainsToolHistoryContent(entry)) {
lastRenderableAssistantIndex = index;
if (pending.length > 0) {
let target = next[index];
for (const item of pending) {
target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText,
});
}
next[index] = target;
pending.length = 0;
changed = true;
}
}
continue;
}
if (!messageContainsToolHistoryContent(entry)) {
continue;
}
const toolName =
typeof entry.toolName === "string"
? entry.toolName
: typeof entry.tool_name === "string"
? entry.tool_name
: undefined;
const text = extractChatHistoryBlockText(entry);
const preview = extractCanvasFromText(text, toolName);
if (!preview) {
continue;
}
pending.push({
preview,
rawText: text ?? null,
});
}
if (pending.length > 0) {
const targetIndex =
lastRenderableAssistantIndex >= 0 ? lastRenderableAssistantIndex : lastAssistantIndex;
if (targetIndex >= 0) {
let target = next[targetIndex];
for (const item of pending) {
target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText,
});
}
next[targetIndex] = target;
changed = true;
}
}
return changed ? next : messages;
}
export function buildOversizedHistoryPlaceholder(message?: unknown): Record<string, unknown> {
const role =
message &&
@@ -2471,71 +2278,6 @@ function isSourceReplyTranscriptMirrorPayload(payload: ReplyPayload | undefined)
return Boolean(payload && getReplyPayloadMetadata(payload)?.sourceReplyTranscriptMirror);
}
function readChatHistoryRecordTimestampMs(message: unknown): number | undefined {
const meta = asOptionalRecord(asOptionalRecord(message)?.["__openclaw"]);
const value = meta?.recordTimestampMs;
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
const timestamp = asOptionalRecord(message)?.timestamp;
return typeof timestamp === "number" && Number.isFinite(timestamp) ? timestamp : undefined;
}
function isSubagentAnnounceInterSessionUserChatHistoryMessage(message: unknown): boolean {
const record = asOptionalRecord(message);
if (!record || record.role !== "user") {
return false;
}
const provenance = normalizeInputProvenance(record.provenance);
if (provenance?.kind === "inter_session" && provenance.sourceTool === "subagent_announce") {
return true;
}
const text = extractChatHistoryBlockText(record);
return (
typeof text === "string" &&
text.includes(INTER_SESSION_PROMPT_PREFIX_BASE) &&
text.includes("sourceTool=subagent_announce")
);
}
function isChatHistoryAssistantMessage(message: unknown): boolean {
return asOptionalRecord(message)?.role === "assistant";
}
export function dropPreSessionStartAnnouncePairs(
messages: unknown[],
sessionStartedAt: number | undefined,
): unknown[] {
if (sessionStartedAt === undefined || messages.length === 0) {
return messages;
}
let changed = false;
const kept: unknown[] = [];
for (let i = 0; i < messages.length; i++) {
const current = messages[i];
if (isSubagentAnnounceInterSessionUserChatHistoryMessage(current)) {
const ts = readChatHistoryRecordTimestampMs(current);
if (typeof ts === "number" && ts < sessionStartedAt) {
const next = messages[i + 1];
const nextTs = readChatHistoryRecordTimestampMs(next);
if (
isChatHistoryAssistantMessage(next) &&
typeof nextTs === "number" &&
nextTs < sessionStartedAt
) {
// Skip only an assistant reply that is also pre-session-start; recent
// or timestampless assistants may be real fresh-session context.
i++;
}
changed = true;
continue;
}
}
kept.push(current);
}
return changed ? kept : messages;
}
function readChatHistoryMessageId(message: unknown): string | undefined {
const metadata = asOptionalRecord(asOptionalRecord(message)?.["__openclaw"]);
return typeof metadata?.id === "string" ? metadata.id : undefined;

View File

@@ -23,19 +23,19 @@ import {
buildSystemRunApprovalEnvBinding,
} from "../../infra/system-run-approval-binding.js";
import { resetLogger, setLoggerOverride } from "../../logging.js";
import { projectRecentChatDisplayMessages } from "../chat-display-projection.js";
import { ExecApprovalManager } from "../exec-approval-manager.js";
import { waitForAgentJob } from "./agent-job.js";
import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.js";
import { normalizeRpcAttachmentsToChatAttachments } from "./attachment-normalize.js";
import {
DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS,
augmentChatHistoryWithCanvasBlocks,
dropPreSessionStartAnnouncePairs,
projectRecentChatDisplayMessages,
resolveEffectiveChatHistoryMaxChars,
sanitizeChatHistoryMessages,
sanitizeChatSendMessageInput,
} from "./chat.js";
} from "../chat-display-projection.js";
import { sanitizeChatSendMessageInput } from "../chat-input-sanitize.js";
import { ExecApprovalManager } from "../exec-approval-manager.js";
import { waitForAgentJob } from "./agent-job.js";
import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.js";
import { normalizeRpcAttachmentsToChatAttachments } from "./attachment-normalize.js";
import { createExecApprovalHandlers } from "./exec-approval.js";
import { logsHandlers } from "./logs.js";

View File

@@ -9,6 +9,7 @@ import { createSubsystemLogger } from "../logging/subsystem.js";
import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
import type { AuthRateLimiter } from "./auth-rate-limit.js";
import type { ResolvedGatewayAuth } from "./auth.js";
import { DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS } from "./chat-display-projection.js";
import {
sendInvalidRequest,
sendJson,
@@ -22,7 +23,6 @@ import {
resolveSharedSecretHttpOperatorScopes,
} from "./http-utils.js";
import { authorizeOperatorScopesForMethod } from "./method-scopes.js";
import { DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS } from "./server-methods/chat.js";
import {
buildSessionHistorySnapshot,
resolveSessionHistoryTailReadOptions,