messageFailure.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738
  1. import type { UiMessage } from '@/components/messages/types'
  2. export type MessageFailure = { title: string; hint: string; detail: string; key: string; replacesContent: boolean }
  3. const failurePrefix = /^执行失败[::]\s*/
  4. /** Presentation only: retain the original transcript and diagnostics for inspection. */
  5. export function messageFailure(message: UiMessage): MessageFailure | undefined {
  6. if (message.communication || message.taskReference || message.role === 'tool') return
  7. if (message.status !== 'failed' && !message.error?.trim()) return
  8. const content = message.content.trim()
  9. const prefixed = message.role !== 'user' && message.status === 'failed' && failurePrefix.test(content)
  10. const error = message.error?.trim() || (prefixed ? content.replace(failurePrefix, '') : '')
  11. if (!error && message.status !== 'failed') return
  12. const providerNotice = message.role === 'assistant' && message.status === 'failed'
  13. && content.includes('Provider said:') && /didn[’']t respond in time on any of \d+ attempts/.test(content)
  14. const replacesContent = message.role !== 'user' && (prefixed || providerNotice || (!!error && content === error) || !content)
  15. const detail = [...new Set([replacesContent ? content.replace(failurePrefix, '') : '', error].filter(Boolean))]
  16. .filter((value, index, values) => !values.some((other, otherIndex) => otherIndex !== index && other.includes(value))).join('\n\n')
  17. const timeout = /timeout|timed?\s*out|no SSE events|didn[’']t respond in time|超时/i.test(error || detail)
  18. return {
  19. title: message.role === 'user' ? '消息未发送' : timeout ? '响应超时' : '回复暂时中断',
  20. hint: message.role === 'user' ? '请检查连接后重新发送。' : '请稍后重试,或切换模型。',
  21. detail, key: error || detail, replacesContent,
  22. }
  23. }
  24. /** Fold only the scheduler's immediately repeated system failure, never a user's message. */
  25. export function collapseDuplicateFailures(messages: UiMessage[]): UiMessage[] {
  26. return messages.filter((message, index) => {
  27. const previous = messages[index - 1]
  28. if (!previous || previous.role !== 'assistant' || message.role !== 'system' || !/^执行失败[::]/.test(message.content)) return true
  29. const failure = messageFailure(message), prior = messageFailure(previous)
  30. if (!failure?.key || failure.key !== prior?.key || message.attachments?.length || message.tools?.length || message.reasoning) return true
  31. if (message.runId && previous.runId) return message.runId !== previous.runId
  32. const at = new Date(message.createdAt ?? '').getTime(), before = new Date(previous.createdAt ?? '').getTime()
  33. return !Number.isFinite(at) || !Number.isFinite(before) || at < before || at - before > 2000
  34. })
  35. }