diff --git a/desktop/src/components/chat/MessageList.test.tsx b/desktop/src/components/chat/MessageList.test.tsx
index fef77b26..5823c4fc 100644
--- a/desktop/src/components/chat/MessageList.test.tsx
+++ b/desktop/src/components/chat/MessageList.test.tsx
@@ -3637,4 +3637,35 @@ describe('MessageList nested tool calls', () => {
),
).toBeTruthy()
})
+
+ it('renders business API errors in the active locale without raw English fallback', () => {
+ useSettingsStore.setState({ locale: 'zh' })
+ useChatStore.setState({
+ sessions: {
+ [ACTIVE_TAB]: makeSessionState({
+ messages: [
+ {
+ id: 'error-1',
+ type: 'error',
+ code: 'invalid_request',
+ businessErrorCode: 'image_unsupported',
+ message:
+ 'This model does not support images. Continue with text, or switch to a vision-capable model and send the image again.',
+ timestamp: 1,
+ },
+ ],
+ }),
+ },
+ })
+
+ render()
+
+ expect(screen.getByText('错误:')).toBeTruthy()
+ expect(
+ screen.getByText(
+ '当前模型不支持图片。请继续使用文字,或切换到支持视觉的模型后重新发送图片。',
+ ),
+ ).toBeTruthy()
+ expect(screen.queryByText(/This model does not support images/)).toBeNull()
+ })
})
diff --git a/desktop/src/components/chat/MessageList.tsx b/desktop/src/components/chat/MessageList.tsx
index db0ef1f0..722b8773 100644
--- a/desktop/src/components/chat/MessageList.tsx
+++ b/desktop/src/components/chat/MessageList.tsx
@@ -2015,16 +2015,26 @@ export const MessageBlock = memo(function MessageBlock({
/>
)
case 'error': {
+ const businessErrorKey = message.businessErrorCode
+ ? `businessError.${message.businessErrorCode}` as TranslationKey
+ : null
+ const businessErrorText = businessErrorKey ? t(businessErrorKey) : null
const errorKey = message.code ? `error.${message.code}` as TranslationKey : null
const errorText = errorKey ? t(errorKey) : null
- const displayMessage = (errorText && errorText !== errorKey) ? errorText : message.message
+ const displayMessage =
+ businessErrorText && businessErrorText !== businessErrorKey
+ ? businessErrorText
+ : (errorText && errorText !== errorKey)
+ ? errorText
+ : message.message
const showRawDetail =
+ !message.businessErrorCode &&
Boolean(message.message) &&
message.message.trim() !== '' &&
message.message !== displayMessage
return (
-
Error: {displayMessage}
+
{t('common.error')}: {displayMessage}
{showRawDetail && (
{message.message}
diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts
index bf497e40..86f13257 100644
--- a/desktop/src/i18n/locales/en.ts
+++ b/desktop/src/i18n/locales/en.ts
@@ -14,6 +14,7 @@ export const en = {
'common.enable': 'Enable',
'common.disable': 'Disable',
'common.active': 'ACTIVE',
+ 'common.error': 'Error',
'common.copyFailed': 'Copy failed.',
// ─── Sidebar ──────────────────────────────────────
@@ -1627,6 +1628,16 @@ export const en = {
'error.NOT_FOUND': 'Resource not found.',
'error.INTERNAL_ERROR': 'Internal server error.',
+ // ─── Business Errors ──────────────────────────────────────
+ 'businessError.pdf_too_large': 'The PDF is too large for the selected model. Convert it to text or use a smaller PDF.',
+ 'businessError.pdf_password_protected': 'The PDF is password protected. Unlock or convert it before sending it again.',
+ 'businessError.pdf_invalid': 'The PDF file is not valid. Convert it to text or send a different file.',
+ 'businessError.image_too_large': 'The image is too large for the selected model. Resize it or send a smaller image.',
+ 'businessError.image_unsupported': 'This model does not support images. Continue with text, or switch to a vision-capable model and send the image again.',
+ 'businessError.request_too_large': 'The request is too large for the selected model. Remove large files or retry with a smaller message.',
+ 'businessError.prompt_too_long': 'The prompt is too long for the selected model. Compact the conversation or retry with less context.',
+ 'businessError.auto_mode_unavailable': 'Auto mode is unavailable for your current plan.',
+
// ─── Server Status Verbs ──────────────────────────────────────
'serverVerb.Thinking': 'Thinking',
'serverVerb.Compacting conversation': 'Compacting context',
diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts
index c91d3038..58216bfd 100644
--- a/desktop/src/i18n/locales/zh.ts
+++ b/desktop/src/i18n/locales/zh.ts
@@ -16,6 +16,7 @@ export const zh: Record = {
'common.enable': '启用',
'common.disable': '禁用',
'common.active': '已激活',
+ 'common.error': '错误',
'common.copyFailed': '复制失败。',
// ─── Sidebar ──────────────────────────────────────
@@ -1629,6 +1630,16 @@ export const zh: Record = {
'error.NOT_FOUND': '资源未找到。',
'error.INTERNAL_ERROR': '内部服务器错误。',
+ // ─── 业务错误 ──────────────────────────────────────
+ 'businessError.pdf_too_large': '这个 PDF 超出了当前模型可处理的大小。请先转成文本,或换一个更小的 PDF。',
+ 'businessError.pdf_password_protected': '这个 PDF 有密码保护。请先解锁或转换后再发送。',
+ 'businessError.pdf_invalid': '这个 PDF 文件无效。请先转成文本,或换一个文件发送。',
+ 'businessError.image_too_large': '这张图片超出了当前模型可处理的大小。请压缩图片,或换一张更小的图片。',
+ 'businessError.image_unsupported': '当前模型不支持图片。请继续使用文字,或切换到支持视觉的模型后重新发送图片。',
+ 'businessError.request_too_large': '这次请求超出了当前模型可处理的大小。请移除大文件,或缩短消息后重试。',
+ 'businessError.prompt_too_long': '当前上下文超出了模型限制。请先压缩会话,或减少上下文后重试。',
+ 'businessError.auto_mode_unavailable': '当前套餐不支持自动模式。',
+
// ─── Server Status Verbs ──────────────────────────────────────
'serverVerb.Thinking': '思考中',
'serverVerb.Compacting conversation': '正在压缩上下文',
diff --git a/desktop/src/stores/chatStore.test.ts b/desktop/src/stores/chatStore.test.ts
index 688f3cb9..5106d8fd 100644
--- a/desktop/src/stores/chatStore.test.ts
+++ b/desktop/src/stores/chatStore.test.ts
@@ -2411,6 +2411,32 @@ describe('chatStore history mapping', () => {
expect(updateTabStatusMock).toHaveBeenLastCalledWith(TEST_SESSION_ID, 'error')
})
+ it('preserves business error codes from server error messages', () => {
+ useChatStore.setState({
+ sessions: {
+ [TEST_SESSION_ID]: makeSession({
+ messages: [],
+ chatState: 'streaming',
+ }),
+ },
+ })
+
+ useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
+ type: 'error',
+ message: 'This model does not support images.',
+ code: 'invalid_request',
+ businessErrorCode: 'image_unsupported',
+ })
+
+ const session = useChatStore.getState().sessions[TEST_SESSION_ID]
+ expect(session?.messages[session.messages.length - 1]).toMatchObject({
+ type: 'error',
+ message: 'This model does not support images.',
+ code: 'invalid_request',
+ businessErrorCode: 'image_unsupported',
+ })
+ })
+
it('removes the transient compacting card when compacting status ends without a boundary', () => {
useChatStore.setState({
sessions: {
diff --git a/desktop/src/stores/chatStore.ts b/desktop/src/stores/chatStore.ts
index dadcb835..30a2650e 100644
--- a/desktop/src/stores/chatStore.ts
+++ b/desktop/src/stores/chatStore.ts
@@ -1649,7 +1649,17 @@ export const useChatStore = create((set, get) => ({
newMessages = appendAssistantTextMessage(newMessages, pendingText, Date.now())
}
newMessages = dropTailCompactingCompactSummary(newMessages)
- newMessages = [...newMessages, { id: nextId(), type: 'error', message: msg.message, code: msg.code, timestamp: Date.now() }]
+ newMessages = [
+ ...newMessages,
+ {
+ id: nextId(),
+ type: 'error',
+ message: msg.message,
+ code: msg.code,
+ ...(msg.businessErrorCode ? { businessErrorCode: msg.businessErrorCode } : {}),
+ timestamp: Date.now(),
+ },
+ ]
return {
messages: newMessages,
chatState: 'idle',
diff --git a/desktop/src/types/chat.ts b/desktop/src/types/chat.ts
index 4819499d..bc0ce247 100644
--- a/desktop/src/types/chat.ts
+++ b/desktop/src/types/chat.ts
@@ -84,7 +84,7 @@ export type ServerMessage =
errorType?: string
errorMessage?: string
}
- | { type: 'error'; message: string; code: string; retryable?: boolean }
+ | { type: 'error'; message: string; code: string; retryable?: boolean; businessErrorCode?: string }
| { type: 'system_notification'; subtype: string; message?: string; data?: unknown }
| { type: 'pong' }
| { type: 'team_update'; teamName: string; members: TeamMemberStatus[] }
@@ -290,5 +290,5 @@ export type UIMessage =
description?: string
timestamp: number
}
- | { id: string; type: 'error'; message: string; code: string; timestamp: number }
+ | { id: string; type: 'error'; message: string; code: string; businessErrorCode?: string; timestamp: number }
| { id: string; type: 'task_summary'; tasks: TaskSummaryItem[]; timestamp: number }
diff --git a/src/constants/businessErrors.ts b/src/constants/businessErrors.ts
new file mode 100644
index 00000000..542f1a6f
--- /dev/null
+++ b/src/constants/businessErrors.ts
@@ -0,0 +1,24 @@
+export const BUSINESS_ERROR_CODES = {
+ PDF_TOO_LARGE: 'pdf_too_large',
+ PDF_PASSWORD_PROTECTED: 'pdf_password_protected',
+ PDF_INVALID: 'pdf_invalid',
+ IMAGE_TOO_LARGE: 'image_too_large',
+ IMAGE_UNSUPPORTED: 'image_unsupported',
+ REQUEST_TOO_LARGE: 'request_too_large',
+ PROMPT_TOO_LONG: 'prompt_too_long',
+ AUTO_MODE_UNAVAILABLE: 'auto_mode_unavailable',
+} as const
+
+export type BusinessErrorCode =
+ (typeof BUSINESS_ERROR_CODES)[keyof typeof BUSINESS_ERROR_CODES]
+
+export const BUSINESS_ERROR_MEDIA_BLOCK_TYPES: Partial<
+ Record
+> = {
+ [BUSINESS_ERROR_CODES.PDF_TOO_LARGE]: ['document'],
+ [BUSINESS_ERROR_CODES.PDF_PASSWORD_PROTECTED]: ['document'],
+ [BUSINESS_ERROR_CODES.PDF_INVALID]: ['document'],
+ [BUSINESS_ERROR_CODES.IMAGE_TOO_LARGE]: ['image'],
+ [BUSINESS_ERROR_CODES.IMAGE_UNSUPPORTED]: ['image'],
+ [BUSINESS_ERROR_CODES.REQUEST_TOO_LARGE]: ['document', 'image'],
+}
diff --git a/src/query.ts b/src/query.ts
index ccd17a0f..ef1251f2 100644
--- a/src/query.ts
+++ b/src/query.ts
@@ -23,6 +23,7 @@ import {
logEvent,
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
} from 'src/services/analytics/index.js'
+import { BUSINESS_ERROR_CODES } from './constants/businessErrors.js'
import { ImageSizeError } from './utils/imageValidation.js'
import { ImageResizeError } from './utils/imageResizer.js'
import { findToolByName, type ToolUseContext } from './Tool.js'
@@ -643,6 +644,7 @@ async function* queryLoop(
yield createAssistantAPIErrorMessage({
content: PROMPT_TOO_LONG_ERROR_MESSAGE,
error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.PROMPT_TOO_LONG,
})
return { reason: 'blocking_limit' }
}
@@ -974,6 +976,7 @@ async function* queryLoop(
) {
yield createAssistantAPIErrorMessage({
content: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_TOO_LARGE,
})
return { reason: 'image_error' }
}
diff --git a/src/server/__tests__/ws-memory-events.test.ts b/src/server/__tests__/ws-memory-events.test.ts
index 0b9cac46..334a9449 100644
--- a/src/server/__tests__/ws-memory-events.test.ts
+++ b/src/server/__tests__/ws-memory-events.test.ts
@@ -6,6 +6,26 @@ import {
import { parseSlashCommand } from '../../utils/slashCommandParsing.js'
describe('WebSocket memory events', () => {
+ it('forwards assistant business error codes to the desktop client', () => {
+ expect(translateCliMessage({
+ type: 'assistant',
+ error: 'invalid_request',
+ isApiErrorMessage: true,
+ businessErrorCode: 'image_unsupported',
+ message: {
+ role: 'assistant',
+ content: [{ type: 'text', text: 'This model does not support images.' }],
+ },
+ }, 'session-1')).toEqual([
+ {
+ type: 'error',
+ message: 'This model does not support images.',
+ code: 'invalid_request',
+ businessErrorCode: 'image_unsupported',
+ },
+ ])
+ })
+
it('forwards CLI memory_saved system messages to the desktop client', () => {
const messages = translateCliMessage(
{
diff --git a/src/server/ws/events.ts b/src/server/ws/events.ts
index d729763d..aba96873 100644
--- a/src/server/ws/events.ts
+++ b/src/server/ws/events.ts
@@ -72,7 +72,7 @@ export type ServerMessage =
errorType?: string
errorMessage?: string
}
- | { type: 'error'; message: string; code: string; retryable?: boolean }
+ | { type: 'error'; message: string; code: string; retryable?: boolean; businessErrorCode?: string }
| { type: 'system_notification'; subtype: string; message?: string; data?: unknown }
| { type: 'pong' }
| { type: 'team_update'; teamName: string; members: TeamMemberStatus[] }
diff --git a/src/server/ws/handler.ts b/src/server/ws/handler.ts
index 37f1bc90..2c51c768 100644
--- a/src/server/ws/handler.ts
+++ b/src/server/ws/handler.ts
@@ -1070,6 +1070,9 @@ export function translateCliMessage(cliMsg: any, sessionId: string): ServerMessa
type: 'error',
message,
code,
+ ...(typeof cliMsg.businessErrorCode === 'string'
+ ? { businessErrorCode: cliMsg.businessErrorCode }
+ : {}),
}]
}
diff --git a/src/services/api/errors.test.ts b/src/services/api/errors.test.ts
index 0d49bb11..dbf5ed02 100644
--- a/src/services/api/errors.test.ts
+++ b/src/services/api/errors.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'bun:test'
+import { BUSINESS_ERROR_CODES } from '../../constants/businessErrors.js'
import {
getAssistantMessageFromError,
getImageUnsupportedErrorMessage,
@@ -29,6 +30,7 @@ describe('image unsupported API errors', () => {
)
expect(msg.isApiErrorMessage).toBe(true)
+ expect(msg.businessErrorCode).toBe(BUSINESS_ERROR_CODES.IMAGE_UNSUPPORTED)
expect(msg.errorDetails).toBe('This model does not support image blocks')
expect(msg.message.content[0]).toMatchObject({
type: 'text',
diff --git a/src/services/api/errors.ts b/src/services/api/errors.ts
index 1b8708eb..bb482455 100644
--- a/src/services/api/errors.ts
+++ b/src/services/api/errors.ts
@@ -8,6 +8,7 @@ import type {
BetaStopReason,
} from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
import { AFK_MODE_BETA_HEADER } from 'src/constants/betas.js'
+import { BUSINESS_ERROR_CODES } from 'src/constants/businessErrors.js'
import type { SDKAssistantMessageError } from 'src/entrypoints/agentSdkTypes.js'
import type {
AssistantMessage,
@@ -495,6 +496,7 @@ export function getAssistantMessageFromError(
if (error instanceof ImageSizeError || error instanceof ImageResizeError) {
return createAssistantAPIErrorMessage({
content: getImageTooLargeErrorMessage(),
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_TOO_LARGE,
})
}
@@ -511,6 +513,7 @@ export function getAssistantMessageFromError(
content: getImageUnsupportedErrorMessage(),
error: 'invalid_request',
errorDetails: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_UNSUPPORTED,
})
}
@@ -633,6 +636,7 @@ export function getAssistantMessageFromError(
content: PROMPT_TOO_LONG_ERROR_MESSAGE,
error: 'invalid_request',
errorDetails: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.PROMPT_TOO_LONG,
})
}
@@ -645,6 +649,7 @@ export function getAssistantMessageFromError(
content: getPdfTooLargeErrorMessage(),
error: 'invalid_request',
errorDetails: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.PDF_TOO_LARGE,
})
}
@@ -656,6 +661,7 @@ export function getAssistantMessageFromError(
return createAssistantAPIErrorMessage({
content: getPdfPasswordProtectedErrorMessage(),
error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.PDF_PASSWORD_PROTECTED,
})
}
@@ -669,6 +675,7 @@ export function getAssistantMessageFromError(
return createAssistantAPIErrorMessage({
content: getPdfInvalidErrorMessage(),
error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.PDF_INVALID,
})
}
@@ -682,6 +689,7 @@ export function getAssistantMessageFromError(
return createAssistantAPIErrorMessage({
content: getImageTooLargeErrorMessage(),
errorDetails: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_TOO_LARGE,
})
}
@@ -698,6 +706,7 @@ export function getAssistantMessageFromError(
: 'An image in the conversation exceeds the dimension limit for many-image requests (2000px). Run /compact to remove old images from context, or start a new session.',
error: 'invalid_request',
errorDetails: error.message,
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_TOO_LARGE,
})
}
@@ -714,6 +723,7 @@ export function getAssistantMessageFromError(
return createAssistantAPIErrorMessage({
content: 'Auto mode is unavailable for your plan',
error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.AUTO_MODE_UNAVAILABLE,
})
}
@@ -723,6 +733,7 @@ export function getAssistantMessageFromError(
return createAssistantAPIErrorMessage({
content: getRequestTooLargeErrorMessage(),
error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.REQUEST_TOO_LARGE,
})
}
diff --git a/src/utils/messages.ts b/src/utils/messages.ts
index 8ea0e002..266b21d4 100644
--- a/src/utils/messages.ts
+++ b/src/utils/messages.ts
@@ -24,6 +24,10 @@ import type { AgentId } from 'src/types/ids.js'
import { companionIntroText } from '../buddy/prompt.js'
import { NO_CONTENT_MESSAGE } from '../constants/messages.js'
import { OUTPUT_STYLE_CONFIG } from '../constants/outputStyles.js'
+import {
+ type BusinessErrorCode,
+ BUSINESS_ERROR_MEDIA_BLOCK_TYPES,
+} from '../constants/businessErrors.js'
import { isAutoMemoryEnabled } from '../memdir/paths.js'
import {
checkStatsigFeatureGate_CACHED_MAY_BE_STALE,
@@ -359,6 +363,7 @@ function baseCreateAssistantMessage({
apiError,
error,
errorDetails,
+ businessErrorCode,
isVirtual,
usage = {
input_tokens: 0,
@@ -381,6 +386,7 @@ function baseCreateAssistantMessage({
apiError?: AssistantMessage['apiError']
error?: SDKAssistantMessageError
errorDetails?: string
+ businessErrorCode?: BusinessErrorCode
isVirtual?: true
usage?: Usage
}): AssistantMessage {
@@ -404,6 +410,7 @@ function baseCreateAssistantMessage({
apiError,
error,
errorDetails,
+ businessErrorCode,
isApiErrorMessage,
isVirtual,
}
@@ -438,11 +445,13 @@ export function createAssistantAPIErrorMessage({
apiError,
error,
errorDetails,
+ businessErrorCode,
}: {
content: string
apiError?: AssistantMessage['apiError']
error?: SDKAssistantMessageError
errorDetails?: string
+ businessErrorCode?: BusinessErrorCode
}): AssistantMessage {
return baseCreateAssistantMessage({
content: [
@@ -455,6 +464,7 @@ export function createAssistantAPIErrorMessage({
apiError,
error,
errorDetails,
+ businessErrorCode,
})
}
@@ -769,6 +779,9 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] {
uuid,
error: message.error,
isApiErrorMessage: message.isApiErrorMessage,
+ apiError: message.apiError,
+ errorDetails: message.errorDetails,
+ businessErrorCode: message.businessErrorCode,
advisorModel: message.advisorModel,
} as NormalizedAssistantMessage
})
@@ -2001,7 +2014,9 @@ export function normalizeMessagesForAPI(
m => !((m.type === 'user' || m.type === 'assistant') && m.isVirtual),
)
- // Build a map from error text → which block types to strip from the preceding user message.
+ // Build a fallback map from legacy error text → which block types to strip
+ // from the preceding user message. New synthetic errors use stable
+ // businessErrorCode values so translated display text cannot break recovery.
const errorToBlockTypes: Record> = {
[getPdfTooLargeErrorMessage()]: new Set(['document']),
[getPdfPasswordProtectedErrorMessage()]: new Set(['document']),
@@ -2019,16 +2034,24 @@ export function normalizeMessagesForAPI(
if (!isSyntheticApiErrorMessage(msg)) {
continue
}
- // Determine which error this is
+ let blockTypesToStrip: Set | undefined
+ const blockTypesFromCode =
+ typeof msg.businessErrorCode === 'string'
+ ? BUSINESS_ERROR_MEDIA_BLOCK_TYPES[msg.businessErrorCode as BusinessErrorCode]
+ : undefined
+ if (blockTypesFromCode) {
+ blockTypesToStrip = new Set(blockTypesFromCode)
+ }
+
+ // Determine which legacy text error this is.
const errorText =
Array.isArray(msg.message.content) &&
msg.message.content[0]?.type === 'text'
? msg.message.content[0].text
: undefined
- if (!errorText) {
- continue
+ if (!blockTypesToStrip && errorText) {
+ blockTypesToStrip = errorToBlockTypes[errorText]
}
- const blockTypesToStrip = errorToBlockTypes[errorText]
if (!blockTypesToStrip) {
continue
}
diff --git a/tests/mediaRecoveryAndEstimation.test.ts b/tests/mediaRecoveryAndEstimation.test.ts
index ecc36f5e..4b75c102 100644
--- a/tests/mediaRecoveryAndEstimation.test.ts
+++ b/tests/mediaRecoveryAndEstimation.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from 'bun:test'
+import { BUSINESS_ERROR_CODES } from '../src/constants/businessErrors.js'
import { getImageUnsupportedErrorMessage } from '../src/services/api/errors.js'
import { roughTokenCountEstimationForAPIRequest } from '../src/services/tokenEstimation.js'
import type { UserMessage } from '../src/types/message.js'
@@ -43,6 +44,32 @@ describe('media error recovery', () => {
expect(serialized).toContain('describe this screenshot')
expect(serialized).toContain('continue with text only')
})
+
+ test('strips images using stable business error codes', () => {
+ const imageUser = createUserMessage({
+ content: [
+ { type: 'text', text: 'describe this screenshot' },
+ imageBlock('base64-image-payload'),
+ ],
+ uuid: '00000000-0000-4000-8000-000000000003',
+ })
+ const unsupported = createAssistantAPIErrorMessage({
+ content: 'localized display text',
+ error: 'invalid_request',
+ businessErrorCode: BUSINESS_ERROR_CODES.IMAGE_UNSUPPORTED,
+ })
+ const nextUser = createUserMessage({
+ content: 'continue with text only',
+ uuid: '00000000-0000-4000-8000-000000000004',
+ })
+
+ const normalized = normalizeMessagesForAPI([imageUser, unsupported, nextUser])
+ const serialized = JSON.stringify(normalized)
+
+ expect(serialized).not.toContain('base64-image-payload')
+ expect(serialized).toContain('describe this screenshot')
+ expect(serialized).toContain('continue with text only')
+ })
})
describe('media context estimation', () => {