diff --git a/client/src/components/Messages/Content/Error.tsx b/client/src/components/Messages/Content/Error.tsx
index 974d54e8af..7242b7decd 100644
--- a/client/src/components/Messages/Content/Error.tsx
+++ b/client/src/components/Messages/Content/Error.tsx
@@ -37,6 +37,22 @@ type TGenericError = {
info: string;
};
+type TContextOverflow = {
+ info?: string;
+ provider?: string;
+ projectedMessageTokens?: number;
+ availableMessageTokens?: number;
+};
+
+/**
+ * SDK boilerplate already covered by the localized headline; whatever remains
+ * (specific guidance and the token budget breakdown) renders as details.
+ */
+const emptyMessagesBoilerplate = [
+ 'Message pruning removed all messages as none fit in the context window.',
+ 'Please increase the context window size or make your message shorter.',
+];
+
const errorMessages = {
[ErrorTypes.MODERATION]: 'com_error_moderation',
[ErrorTypes.NO_USER_KEY]: 'com_error_no_user_key',
@@ -79,6 +95,39 @@ const errorMessages = {
[ErrorTypes.GOOGLE_VIDEO_UNPROCESSABLE]: 'com_error_google_video_unprocessable',
[ErrorTypes.RESOURCE_RECOVERY_REQUIRED]: 'com_error_resource_recovery_required',
[ErrorTypes.STREAM_EXPIRED]: 'com_error_stream_expired',
+ [ErrorTypes.EMPTY_MESSAGES]: (json: TGenericError, localize: LocalizeFunction) => {
+ const detail = emptyMessagesBoilerplate
+ .reduce((info, sentence) => info.replace(sentence, ''), json.info ?? '')
+ .trim();
+ return (
+ <>
+ {localize('com_error_empty_messages')}
+ {detail && (
+ <>
+
+
+
+ >
+ )}
+ >
+ );
+ },
+ [ErrorTypes.FINAL_CONTEXT_OVERFLOW]: (json: TContextOverflow, localize: LocalizeFunction) => {
+ const { projectedMessageTokens: projected, availableMessageTokens: available } = json;
+ const message = localize('com_error_final_context_overflow');
+ if (typeof projected !== 'number' || typeof available !== 'number') {
+ return message;
+ }
+ return `${message} ${localize('com_error_context_tokens_detail', {
+ 0: projected,
+ 1: available,
+ })}`;
+ },
[ViolationTypes.BAN]:
'Your account has been temporarily banned due to violations of our service.',
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: (json: TGenericError, localize: LocalizeFunction) => {
diff --git a/client/src/components/Messages/Content/__tests__/Error.spec.tsx b/client/src/components/Messages/Content/__tests__/Error.spec.tsx
index 5e2bc1ce76..cbef66151c 100644
--- a/client/src/components/Messages/Content/__tests__/Error.spec.tsx
+++ b/client/src/components/Messages/Content/__tests__/Error.spec.tsx
@@ -11,8 +11,16 @@ import Error from '../Error';
jest.mock('~/hooks', () => ({
useLocalize:
() =>
- (key: string): string =>
- (jest.requireActual('~/locales/en/translation.json') as Record)[key] ?? key,
+ (key: string, values?: Record): string => {
+ const template =
+ (jest.requireActual('~/locales/en/translation.json') as Record)[key] ?? key;
+ if (!values) {
+ return template;
+ }
+ return template.replace(/\{\{(\w+)\}\}/g, (match, name) =>
+ values[name] != null ? String(values[name]) : match,
+ );
+ },
}));
const catalog = translation as Record;
@@ -49,3 +57,56 @@ describe('Error — typed provider errors', () => {
).toBeInTheDocument();
});
});
+
+describe('Error — agent context budget errors', () => {
+ beforeAll(() => {
+ /** CodeBlock observes its code bar; jsdom ships no IntersectionObserver. */
+ (global as { IntersectionObserver?: unknown }).IntersectionObserver = class {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ };
+ });
+
+ it('renders localized copy and the token breakdown for empty_messages', () => {
+ /** Mirrors the SDK payload as embedded by the agents controller's error prefix. */
+ const info =
+ 'Message pruning removed all messages as none fit in the context window. Please increase the context window size or make your message shorter.\nToken budget breakdown:\n maxContextTokens: 10\n messageTokens: 32 (4 messages)\n availableForMessages: 9';
+ const payload = `An error occurred while processing the request: ${JSON.stringify({
+ type: ErrorTypes.EMPTY_MESSAGES,
+ info,
+ })}`;
+ render();
+
+ expect(screen.getByText(catalog.com_error_empty_messages)).toBeInTheDocument();
+ expect(screen.getByText(/Token budget breakdown/)).toBeInTheDocument();
+ expect(screen.queryByText(/Something went wrong/)).not.toBeInTheDocument();
+ });
+
+ it('renders localized copy with token counts for final_context_overflow', () => {
+ const payload = `An error occurred while processing the request: ${JSON.stringify({
+ type: ErrorTypes.FINAL_CONTEXT_OVERFLOW,
+ info: 'Provider message formatting exceeded the context budget and no safe synthetic-context compaction could make it fit.',
+ provider: 'openAI',
+ projectedMessageTokens: 3,
+ availableMessageTokens: 0,
+ })}`;
+ render();
+
+ expect(
+ screen.getByText(new RegExp(catalog.com_error_final_context_overflow.slice(0, 40))),
+ ).toBeInTheDocument();
+ expect(screen.getByText(/need 3 tokens, but only 0 are available/)).toBeInTheDocument();
+ expect(screen.queryByText(/Something went wrong/)).not.toBeInTheDocument();
+ });
+
+ it('falls back to the plain sentence when overflow token counts are absent', () => {
+ const payload = JSON.stringify({
+ type: ErrorTypes.FINAL_CONTEXT_OVERFLOW,
+ info: 'Fallback provider message formatting exceeded the context budget before invocation.',
+ });
+ render();
+
+ expect(screen.getByText(catalog.com_error_final_context_overflow)).toBeInTheDocument();
+ });
+});
diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json
index 808a9d3a1b..85e58c2074 100644
--- a/client/src/locales/en/translation.json
+++ b/client/src/locales/en/translation.json
@@ -380,12 +380,15 @@
"com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.",
"com_error_files_dupe": "Duplicate file detected.",
"com_error_files_empty": "Empty files are not allowed.",
+ "com_error_context_tokens_detail": "Your messages need {{0}} tokens, but only {{1}} are available.",
+ "com_error_empty_messages": "Your messages do not fit in the current context window, so there was nothing left to send to the model. Increase the max context size from the conversation parameters, shorten your message, or start a new conversation.",
"com_error_files_process": "An error occurred while processing the file.",
"com_error_files_unsupported": "This file type can't be attached here.",
"com_error_files_upload": "An error occurred while uploading the file.",
"com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.",
"com_error_files_upload_too_large": "The file is too large. Please upload a file smaller than {{0}} MB",
"com_error_files_validation": "An error occurred while validating the file.",
+ "com_error_final_context_overflow": "The request exceeds the current context window and could not be reduced to fit. Increase the max context size from the conversation parameters or shorten your message.",
"com_error_google_tool_conflict": "Usage of built-in Google tools are not supported with external tools. Please disable either the built-in tools or the external tools.",
"com_error_google_video_unprocessable": "The linked video could not be processed. It is most likely too long for this model, but it may also be unavailable in this region or restricted. Try a shorter video, or describe the relevant parts in your message instead.",
"com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.",
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index 6e91fc2452..e8ba7c7bef 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -2950,6 +2950,14 @@ export enum ErrorTypes {
* SSE stream 404 — job completed, expired, or was deleted before the subscriber connected
*/
STREAM_EXPIRED = 'stream_expired',
+ /**
+ * Context pruning removed every message; nothing fits the configured context window
+ */
+ EMPTY_MESSAGES = 'empty_messages',
+ /**
+ * Formatted provider payload exceeded the context budget before invocation
+ */
+ FINAL_CONTEXT_OVERFLOW = 'final_context_overflow',
}
/**