🧷 fix: Bind Agent File Context to Current Turn (#13506)

* fix: Bind agent file context to current turn

* fix: Avoid duplicating agent file context

* fix: Export agent file context prepender

* test: Use exported file context prepender

* fix: Keep file context transient for memory and counts
This commit is contained in:
Danny Avila 2026-06-04 09:03:43 -04:00 committed by GitHub
parent a1bfa3b298
commit dc42748813
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 249 additions and 45 deletions

View file

@ -0,0 +1,54 @@
import { ContentTypes } from 'librechat-data-provider';
import { prependFileContext, type FormattedMessageWithContent } from './client';
describe('prependFileContext', () => {
it('prepends file context to string content', () => {
const message: FormattedMessageWithContent = { content: 'Answer this question.' };
prependFileContext(message, 'Attached file text');
expect(message.content).toBe('Attached file text\nAnswer this question.');
});
it('prepends file context to the first text content part', () => {
const message: FormattedMessageWithContent = {
content: [
{ type: ContentTypes.IMAGE_URL, image_url: { url: 'data:image/png;base64,abc' } },
{ type: ContentTypes.TEXT, text: 'Answer this question.' },
],
};
prependFileContext(message, 'Attached file text');
expect(Array.isArray(message.content)).toBe(true);
if (!Array.isArray(message.content)) {
throw new Error('Expected array content');
}
expect(message.content[1].text).toBe('Attached file text\nAnswer this question.');
expect(message.content[0]).toEqual({
type: ContentTypes.IMAGE_URL,
image_url: { url: 'data:image/png;base64,abc' },
});
});
it('adds a text content part when an array has no text part', () => {
const message: FormattedMessageWithContent = {
content: [{ type: ContentTypes.IMAGE_URL, image_url: { url: 'data:image/png;base64,abc' } }],
};
prependFileContext(message, 'Attached file text');
expect(message.content).toEqual([
{ type: ContentTypes.TEXT, text: 'Attached file text' },
{ type: ContentTypes.IMAGE_URL, image_url: { url: 'data:image/png;base64,abc' } },
]);
});
it('leaves content unchanged when file context is empty', () => {
const message: FormattedMessageWithContent = { content: 'Answer this question.' };
prependFileContext(message, '');
expect(message.content).toBe('Answer this question.');
});
});

View file

@ -56,6 +56,42 @@ type ContentBlock = {
tool_call?: { name?: string; args?: string; output?: string };
};
export type FormattedMessageContentPart = {
type?: string;
text?: string;
[key: string]: unknown;
};
export type FormattedMessageWithContent = {
content?: string | FormattedMessageContentPart[];
};
export function prependFileContext(
formattedMessage: FormattedMessageWithContent,
fileContext?: string | null,
): void {
if (!fileContext) {
return;
}
if (typeof formattedMessage.content === 'string') {
formattedMessage.content = `${fileContext}\n${formattedMessage.content}`;
return;
}
if (!Array.isArray(formattedMessage.content)) {
return;
}
const textPart = formattedMessage.content.find((part) => part.type === ContentTypes.TEXT);
if (textPart != null && typeof textPart.text === 'string') {
textPart.text = `${fileContext}\n${textPart.text}`;
return;
}
formattedMessage.content.unshift({ type: ContentTypes.TEXT, text: fileContext });
}
function estimateImageDataTokens(data: string, isClaude: boolean): number {
const dims = extractImageDimensions(data);
if (dims == null) {