Files
openclaw/src/gateway/server-methods/chat-message-get-handler.ts
T
Peter Steinberger 84a4ff30d5 refactor(gateway): centralize parameter validation and shared helpers (#114353)
* refactor(gateway): centralize parameter validation

* refactor(outbound): centralize runtime scaffolding stripping

* refactor(usage): remove session cost forwarding layers

* refactor(gateway): share attachment failure logging

* refactor(gateway): centralize startup tracing helpers

* fix(usage): keep refresh result type internal

* fix(outbound): preserve visible whitespace around runtime context
2026-07-27 05:34:12 -04:00

153 lines
5.1 KiB
TypeScript

// Single-message lookup applies the same visibility and display projection as chat.history.
import {
ErrorCodes,
errorShape,
validateChatMessageGetParams,
} from "../../../packages/gateway-protocol/src/index.js";
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import {
augmentChatHistoryWithCanvasBlocks,
dropPreSessionStartAnnouncePairs,
projectChatDisplayMessage,
} from "../chat-display-projection.js";
import { MAX_PAYLOAD_BYTES } from "../server-constants.js";
import {
readSessionMessageByIdAsync,
readSessionMessagesAsync,
} from "../session-transcript-readers.js";
import { loadSessionEntryReadOnly } from "../session-utils.js";
import { readChatHistoryMessageId } from "./chat-history-pages.js";
import { resolveRequestedChatAgentId, validateChatSelectedAgent } from "./chat-origin-routing.js";
import { normalizeOptionalChatText as normalizeOptionalText } from "./chat-text-normalization.js";
import type { GatewayRequestHandlers } from "./types.js";
import { assertValidParams } from "./validation.js";
async function isChatMessageIdVisibleAfterHistoryFilters(params: {
sessionId: string;
storePath: string | undefined;
sessionEntry?: { sessionFile?: string; sessionId?: string };
sessionKey: string;
agentId?: string;
messageId: string;
sessionStartedAt?: number;
allowResetArchiveFallback?: boolean;
}): Promise<boolean> {
if (params.sessionStartedAt === undefined) {
return true;
}
const messages = await readSessionMessagesAsync(
{
agentId: params.agentId,
sessionEntry: params.sessionEntry,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
storePath: params.storePath,
},
{
mode: "full",
reason: "chat.message.get visibility",
...(params.allowResetArchiveFallback === true ? { allowResetArchiveFallback: true } : {}),
},
);
return dropPreSessionStartAnnouncePairs(messages, params.sessionStartedAt).some(
(message) => readChatHistoryMessageId(message) === params.messageId,
);
}
export const chatMessageGetHandlers: GatewayRequestHandlers = {
"chat.message.get": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateChatMessageGetParams, "chat.message.get", respond)) {
return;
}
const { sessionKey, messageId, maxChars } = params as {
sessionKey: string;
agentId?: string;
messageId: string;
maxChars?: number;
};
const agentIdOverride = normalizeOptionalText((params as { agentId?: string }).agentId);
const requestedAgentId = resolveRequestedChatAgentId({
cfg: (context as { getRuntimeConfig?: () => OpenClawConfig }).getRuntimeConfig?.(),
requestedSessionKey: sessionKey,
agentId: agentIdOverride,
});
const sessionLoadOptions = requestedAgentId ? { agentId: requestedAgentId } : undefined;
const { cfg, storePath, entry } = loadSessionEntryReadOnly(sessionKey, sessionLoadOptions);
const selectedAgent = validateChatSelectedAgent({
cfg,
requestedSessionKey: sessionKey,
agentId: requestedAgentId,
});
if (!selectedAgent.ok) {
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, selectedAgent.error));
return;
}
const sessionId = entry?.sessionId;
if (!sessionId) {
respond(true, { ok: false, unavailableReason: "not_found" });
return;
}
const sessionAgentId = resolveSessionAgentId({
sessionKey,
config: cfg,
agentId: selectedAgent.agentId,
});
const resolved = await readSessionMessageByIdAsync(
{
agentId: sessionAgentId,
sessionEntry: entry,
sessionId,
sessionKey,
storePath,
},
messageId,
{ allowResetArchiveFallback: true },
);
if (!resolved.found) {
respond(true, { ok: false, unavailableReason: "not_found" });
return;
}
const visible = await isChatMessageIdVisibleAfterHistoryFilters({
sessionId,
storePath,
sessionEntry: entry,
sessionKey,
agentId: sessionAgentId,
messageId,
sessionStartedAt:
typeof entry?.sessionStartedAt === "number" ? entry.sessionStartedAt : undefined,
allowResetArchiveFallback: true,
});
if (!visible) {
respond(true, { ok: false, unavailableReason: "not_found" });
return;
}
if (resolved.oversized) {
respond(true, { ok: false, unavailableReason: "oversized" });
return;
}
const effectiveMaxChars =
typeof maxChars === "number" ? maxChars : Math.min(MAX_PAYLOAD_BYTES, 1_000_000);
const projectedMessage = resolved.message
? projectChatDisplayMessage(resolved.message, {
maxChars: effectiveMaxChars,
})
: undefined;
const projected = projectedMessage
? augmentChatHistoryWithCanvasBlocks([projectedMessage])[0]
: undefined;
if (!projected) {
respond(true, { ok: false, unavailableReason: "not_visible" });
return;
}
respond(true, {
ok: true,
message: projected,
});
},
};