mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
perf: avoid decoding attachment buffers during send preflight (#147879)
This commit is contained in:
parent
b790d46d04
commit
e89c182094
2 changed files with 49 additions and 27 deletions
|
|
@ -794,10 +794,35 @@ describe("message action media helpers", () => {
|
|||
|
||||
it.each(
|
||||
["dry-run", "preserve-buffer"].flatMap((mode) => [
|
||||
{ mode, buffer: "SGVsbG8=", name: "raw base64" },
|
||||
{ mode, buffer: "data:application/octet-stream;base64,SGVsbG8=", name: "data URL" },
|
||||
{ mode, buffer: "SGVsbG8=", name: "raw base64", expectedError: undefined },
|
||||
{
|
||||
mode,
|
||||
buffer: "data:application/octet-stream;base64,SGVsbG8=",
|
||||
name: "data URL",
|
||||
expectedError: undefined,
|
||||
},
|
||||
{ mode, buffer: "SGVsbG8", name: "unpadded base64", expectedError: undefined },
|
||||
{ mode, buffer: " SGV s bG8= \n", name: "whitespace base64", expectedError: undefined },
|
||||
{
|
||||
mode,
|
||||
buffer: "SGVsbG8h",
|
||||
name: "one byte over the limit",
|
||||
expectedError: "Media too large: 6 bytes (limit: 5 bytes)",
|
||||
},
|
||||
{
|
||||
mode,
|
||||
buffer: "!!!!!!!!",
|
||||
name: "oversized malformed base64",
|
||||
expectedError: "Media too large: 6 bytes (limit: 5 bytes)",
|
||||
},
|
||||
{
|
||||
mode,
|
||||
buffer: " \t\r\n",
|
||||
name: "whitespace-only base64",
|
||||
expectedError: "message.send buffer has invalid base64 data",
|
||||
},
|
||||
]),
|
||||
)("keeps explicit MIME for $mode $name without staging", async ({ mode, buffer }) => {
|
||||
)("validates $mode $name without staging", async ({ mode, buffer, expectedError }) => {
|
||||
await withTempOpenClawStateDir(async (stateDir) => {
|
||||
const args: Record<string, unknown> = {
|
||||
buffer,
|
||||
|
|
@ -805,8 +830,8 @@ describe("message action media helpers", () => {
|
|||
mimeType: "text/plain",
|
||||
};
|
||||
|
||||
await hydrateAttachmentParamsForAction({
|
||||
cfg,
|
||||
const hydration = hydrateAttachmentParamsForAction({
|
||||
cfg: { agents: { defaults: { mediaMaxMb: 5 / (1024 * 1024) } } },
|
||||
channel: "imessage",
|
||||
args,
|
||||
action: "send",
|
||||
|
|
@ -815,12 +840,18 @@ describe("message action media helpers", () => {
|
|||
mediaPolicy: { mode: "host" },
|
||||
});
|
||||
|
||||
expect(args.media).toBe("buffer://message-send/attachment");
|
||||
expect(args.mediaUrl).toBe("buffer://message-send/attachment");
|
||||
expect(args.mediaUrls).toEqual(["buffer://message-send/attachment"]);
|
||||
expect(args.buffer).toBe(mode === "preserve-buffer" ? buffer : undefined);
|
||||
expect(args.contentType).toBe("text/plain");
|
||||
expect(args.filename).toBe("preview.txt");
|
||||
if (expectedError) {
|
||||
await expect(hydration).rejects.toThrow(expectedError);
|
||||
expect(args).toEqual({ buffer, filename: "preview.txt", mimeType: "text/plain" });
|
||||
} else {
|
||||
await hydration;
|
||||
expect(args.media).toBe("buffer://message-send/attachment");
|
||||
expect(args.mediaUrl).toBe("buffer://message-send/attachment");
|
||||
expect(args.mediaUrls).toEqual(["buffer://message-send/attachment"]);
|
||||
expect(args.buffer).toBe(mode === "preserve-buffer" ? buffer : undefined);
|
||||
expect(args.contentType).toBe("text/plain");
|
||||
expect(args.filename).toBe("preview.txt");
|
||||
}
|
||||
await expect(fs.readdir(path.join(stateDir, "media", "outbound"))).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ function resolveSendBufferMaxBytes(params: {
|
|||
);
|
||||
}
|
||||
|
||||
function decodeBoundedBase64Attachment(params: { base64: string; maxBytes: number }): Buffer {
|
||||
function validateBoundedBase64Attachment(params: { base64: string; maxBytes: number }): string {
|
||||
const estimatedBytes = estimateBase64DecodedBytes(params.base64);
|
||||
if (estimatedBytes > params.maxBytes) {
|
||||
throw new Error(`Media too large: ${estimatedBytes} bytes (limit: ${params.maxBytes} bytes)`);
|
||||
|
|
@ -307,13 +307,7 @@ function decodeBoundedBase64Attachment(params: { base64: string; maxBytes: numbe
|
|||
if (!canonicalBase64) {
|
||||
throw new Error("message.send buffer has invalid base64 data");
|
||||
}
|
||||
const buffer = Buffer.from(canonicalBase64, "base64");
|
||||
if (buffer.byteLength > params.maxBytes) {
|
||||
throw new Error(
|
||||
`Media too large: ${buffer.byteLength} bytes (limit: ${params.maxBytes} bytes)`,
|
||||
);
|
||||
}
|
||||
return buffer;
|
||||
return canonicalBase64;
|
||||
}
|
||||
|
||||
async function hydrateSendBufferMediaParams(params: {
|
||||
|
|
@ -348,11 +342,11 @@ async function hydrateSendBufferMediaParams(params: {
|
|||
contentType: normalized.contentType,
|
||||
});
|
||||
const maxBytes = resolveSendBufferMaxBytes(params);
|
||||
const canonicalBase64 = validateBoundedBase64Attachment({
|
||||
base64: normalized.base64,
|
||||
maxBytes,
|
||||
});
|
||||
if (params.dryRun || params.preserveBuffer) {
|
||||
decodeBoundedBase64Attachment({
|
||||
base64: normalized.base64,
|
||||
maxBytes,
|
||||
});
|
||||
params.args.media = SEND_BUFFER_DRY_RUN_MEDIA_URL;
|
||||
params.args.mediaUrl = SEND_BUFFER_DRY_RUN_MEDIA_URL;
|
||||
params.args.mediaUrls = [SEND_BUFFER_DRY_RUN_MEDIA_URL];
|
||||
|
|
@ -368,10 +362,7 @@ async function hydrateSendBufferMediaParams(params: {
|
|||
return;
|
||||
}
|
||||
const staged = await resolveOutboundAttachmentFromBuffer(
|
||||
decodeBoundedBase64Attachment({
|
||||
base64: normalized.base64,
|
||||
maxBytes,
|
||||
}),
|
||||
Buffer.from(canonicalBase64, "base64"),
|
||||
maxBytes,
|
||||
{
|
||||
contentType: normalized.contentType,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue