fix: stop inferring file extraction from Body/RawBody mismatch (#46454)

This commit is contained in:
Joey Krug 2026-03-15 21:54:48 -04:00
parent e8fd176cf4
commit 1b6c43baa4
3 changed files with 62 additions and 20 deletions

View File

@ -292,27 +292,21 @@ export async function applyDeferredMediaUnderstandingToQueuedRun(
return;
}
const referenceBody = mediaContext.RawBody ?? mediaContext.Body;
// Prefer RawBody-vs-Body comparison when RawBody exists. If RawBody is
// missing, any real <file>...</file> block plus file-like attachments means
// extraction already ran, even if the stored name came from Content-Disposition
// instead of the attachment path/url basename.
if (!mediaContext.DeferredFileBlocksExtracted && hasAnyFileAttachments(mediaContext)) {
const rawBodyMissing = typeof mediaContext.RawBody !== "string";
if (mediaContext.Body !== referenceBody) {
mediaContext.DeferredFileBlocksExtracted = true;
} else if (
rawBodyMissing &&
(Boolean(mediaContext.MediaUnderstanding?.length) ||
bodyContainsExtractedFileBlock(mediaContext.Body))
) {
mediaContext.DeferredFileBlocksExtracted = true;
}
}
if (mediaContext.MediaUnderstanding?.length) {
mediaContext.DeferredMediaApplied = true;
return;
}
// Treat followup file extraction as already applied only when we have explicit
// evidence: the queue snapshot already flagged it or Body already contains a
// real extracted <file>...</file> block. Body/RawBody mismatches are not
// reliable because some channels wrap Body with envelope metadata.
if (
!mediaContext.DeferredFileBlocksExtracted &&
hasAnyFileAttachments(mediaContext) &&
bodyContainsExtractedFileBlock(mediaContext.Body)
) {
mediaContext.DeferredFileBlocksExtracted = true;
}
if (mediaContext.DeferredFileBlocksExtracted && hasOnlyFileLikeAttachments(mediaContext)) {
mediaContext.DeferredMediaApplied = true;

View File

@ -1231,6 +1231,54 @@ describe("createFollowupRunner media understanding", () => {
expect(agentCall?.prompt?.match(/<file\b/g)).toHaveLength(1);
});
it("does not infer file extraction from wrapped Body/RawBody mismatches", async () => {
const fileBlock = '<file name="report.pdf" mime="application/pdf">\nreport content\n</file>';
applyMediaUnderstandingMock.mockImplementationOnce(
async (params: { ctx: Record<string, unknown> }) => {
params.ctx.Body = `summarize this\n\n${fileBlock}`;
return {
outputs: [],
decisions: [],
appliedImage: false,
appliedAudio: false,
appliedVideo: false,
appliedFile: true,
};
},
);
runEmbeddedPiAgentMock.mockResolvedValueOnce({
payloads: [{ text: "file processed" }],
meta: {},
});
const runner = createFollowupRunner({
opts: { onBlockReply: vi.fn(async () => {}) },
typing: createMockTypingController(),
typingMode: "instant",
defaultModel: "anthropic/claude-opus-4-5",
});
await runner(
createQueuedRun({
prompt: "[media attached: /tmp/report.pdf]\nLine: Alice\nsummarize this",
mediaContext: {
Body: "Line: Alice\nsummarize this",
RawBody: "summarize this",
MediaPaths: ["/tmp/report.pdf"],
MediaTypes: ["application/pdf"],
},
}),
);
expect(applyMediaUnderstandingMock).toHaveBeenCalledTimes(1);
const agentCall = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as {
prompt?: string;
};
expect(agentCall?.prompt).toContain("Line: Alice");
expect(agentCall?.prompt).toContain(fileBlock);
expect(agentCall?.prompt?.match(/<file\s+name="report\.pdf"/g)).toHaveLength(1);
});
it("preserves non-audio media lines when only audio is transcribed", async () => {
applyMediaUnderstandingMock.mockImplementationOnce(
async (params: { ctx: Record<string, unknown> }) => {

View File

@ -52,9 +52,9 @@ export type FollowupMediaContext = {
DeferredMediaApplied?: boolean;
/**
* Set when file extraction has already been applied to Body (either in the
* primary path or by a previous deferred-media run). Checked instead of
* scanning body text for `<file` patterns to avoid false-positives on user
* messages that contain literal XML-like text.
* primary path or by a previous deferred-media run). This avoids re-running
* file extraction when Body already contains real extracted `<file>...</file>`
* blocks.
*/
DeferredFileBlocksExtracted?: boolean;
};