mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 15:49:29 +08:00
Fix/aisdk error (#10563)
* Add syntax highlighting to AI SDK error cause display - Parse and format error cause as JSON with syntax highlighting - Use CodeStyleProvider context for consistent code styling - Maintain plain text fallback for non-JSON content * fix patch * chore: yarn lock * feat: provider-specific-error * chore * chore * fix: handle JSON parsing errors in AiSdkErrorBase component * fix: improve error message formatting in AiSdkToChunkAdapter * fix: remove unused MarkdownContainer and update AiSdkErrorBase to use styled div
This commit is contained in:
parent
a33e3971d9
commit
726b2570e2
@ -6,6 +6,8 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { AISDKWebSearchResult, MCPTool, WebSearchResults, WebSearchSource } from '@renderer/types'
|
import { AISDKWebSearchResult, MCPTool, WebSearchResults, WebSearchSource } from '@renderer/types'
|
||||||
import { Chunk, ChunkType } from '@renderer/types/chunk'
|
import { Chunk, ChunkType } from '@renderer/types/chunk'
|
||||||
|
import { ProviderSpecificError } from '@renderer/types/provider-specific-error'
|
||||||
|
import { formatErrorMessage } from '@renderer/utils/error'
|
||||||
import { convertLinks, flushLinkConverterBuffer } from '@renderer/utils/linkConverter'
|
import { convertLinks, flushLinkConverterBuffer } from '@renderer/utils/linkConverter'
|
||||||
import type { TextStreamPart, ToolSet } from 'ai'
|
import type { TextStreamPart, ToolSet } from 'ai'
|
||||||
|
|
||||||
@ -340,7 +342,11 @@ export class AiSdkToChunkAdapter {
|
|||||||
case 'error':
|
case 'error':
|
||||||
this.onChunk({
|
this.onChunk({
|
||||||
type: ChunkType.ERROR,
|
type: ChunkType.ERROR,
|
||||||
error: chunk.error as Record<string, any>
|
error: new ProviderSpecificError({
|
||||||
|
message: formatErrorMessage(chunk.error),
|
||||||
|
provider: 'unknown',
|
||||||
|
cause: chunk.error
|
||||||
|
})
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Button } from '@heroui/button'
|
import { Button } from '@heroui/button'
|
||||||
import CodeViewer from '@renderer/components/CodeViewer'
|
import CodeViewer from '@renderer/components/CodeViewer'
|
||||||
|
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label'
|
import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label'
|
||||||
import { getProviderById } from '@renderer/services/ProviderService'
|
import { getProviderById } from '@renderer/services/ProviderService'
|
||||||
@ -35,7 +36,7 @@ import {
|
|||||||
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
||||||
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
||||||
import { Alert as AntdAlert, Modal } from 'antd'
|
import { Alert as AntdAlert, Modal } from 'antd'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -305,14 +306,36 @@ const BuiltinError = ({ error }: { error: SerializedError }) => {
|
|||||||
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
||||||
const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { highlightCode } = useCodeStyle()
|
||||||
|
const [highlightedString, setHighlightedString] = useState('')
|
||||||
const cause = error.cause
|
const cause = error.cause
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const highlight = async () => {
|
||||||
|
try {
|
||||||
|
const result = await highlightCode(JSON.stringify(JSON.parse(cause || '{}'), null, 2), 'json')
|
||||||
|
setHighlightedString(result)
|
||||||
|
} catch {
|
||||||
|
setHighlightedString(cause || '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const timer = setTimeout(highlight, 0)
|
||||||
|
|
||||||
|
return () => clearTimeout(timer)
|
||||||
|
}, [highlightCode, cause])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BuiltinError error={error} />
|
<BuiltinError error={error} />
|
||||||
{cause && (
|
{cause && (
|
||||||
<ErrorDetailItem>
|
<ErrorDetailItem>
|
||||||
<ErrorDetailLabel>{t('error.cause')}:</ErrorDetailLabel>
|
<ErrorDetailLabel>{t('error.cause')}:</ErrorDetailLabel>
|
||||||
<ErrorDetailValue>{error.cause}</ErrorDetailValue>
|
<ErrorDetailValue>
|
||||||
|
<div
|
||||||
|
className="markdown [&_pre]:!bg-transparent [&_pre_span]:whitespace-pre-wrap"
|
||||||
|
dangerouslySetInnerHTML={{ __html: highlightedString }}
|
||||||
|
/>
|
||||||
|
</ErrorDetailValue>
|
||||||
</ErrorDetailItem>
|
</ErrorDetailItem>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
UnsupportedFunctionalityError
|
UnsupportedFunctionalityError
|
||||||
} from 'ai'
|
} from 'ai'
|
||||||
|
|
||||||
|
import { ProviderSpecificError } from './provider-specific-error'
|
||||||
import { Serializable } from './serialize'
|
import { Serializable } from './serialize'
|
||||||
|
|
||||||
export interface SerializedError {
|
export interface SerializedError {
|
||||||
@ -80,7 +81,7 @@ export interface SerializedAiSdkInvalidArgumentError extends SerializedAiSdkErro
|
|||||||
export const isSerializedAiSdkInvalidArgumentError = (
|
export const isSerializedAiSdkInvalidArgumentError = (
|
||||||
error: SerializedError
|
error: SerializedError
|
||||||
): error is SerializedAiSdkInvalidArgumentError => {
|
): error is SerializedAiSdkInvalidArgumentError => {
|
||||||
return isSerializedAiSdkError(error) && 'parameter' in error && 'value' in error
|
return isSerializedAiSdkError(error) && 'message' in error && error.name === 'AI_InvalidArgumentError'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerializedAiSdkInvalidDataContentError extends SerializedAiSdkError {
|
export interface SerializedAiSdkInvalidDataContentError extends SerializedAiSdkError {
|
||||||
@ -198,10 +199,20 @@ export interface SerializedAiSdkNoSuchToolError extends SerializedAiSdkError {
|
|||||||
readonly availableTools: string[] | null
|
readonly availableTools: string[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SerializedAiSdkProviderSpecificError extends SerializedAiSdkError {
|
||||||
|
readonly provider: string
|
||||||
|
}
|
||||||
|
|
||||||
export const isSerializedAiSdkNoSuchToolError = (error: SerializedError): error is SerializedAiSdkNoSuchToolError => {
|
export const isSerializedAiSdkNoSuchToolError = (error: SerializedError): error is SerializedAiSdkNoSuchToolError => {
|
||||||
return isSerializedAiSdkError(error) && 'toolName' in error && 'availableTools' in error
|
return isSerializedAiSdkError(error) && 'toolName' in error && 'availableTools' in error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isSerializedAiSdkProviderSpecificError = (
|
||||||
|
error: SerializedError
|
||||||
|
): error is SerializedAiSdkProviderSpecificError => {
|
||||||
|
return isSerializedAiSdkError(error) && 'provider' in error
|
||||||
|
}
|
||||||
|
|
||||||
export interface SerializedAiSdkRetryError extends SerializedAiSdkError {
|
export interface SerializedAiSdkRetryError extends SerializedAiSdkError {
|
||||||
readonly reason: string
|
readonly reason: string
|
||||||
readonly lastError: Serializable
|
readonly lastError: Serializable
|
||||||
@ -277,6 +288,7 @@ export type AiSdkErrorUnion =
|
|||||||
| NoSuchModelError
|
| NoSuchModelError
|
||||||
| NoSuchProviderError
|
| NoSuchProviderError
|
||||||
| NoSuchToolError
|
| NoSuchToolError
|
||||||
|
| ProviderSpecificError
|
||||||
| RetryError
|
| RetryError
|
||||||
| ToolCallRepairError
|
| ToolCallRepairError
|
||||||
| TypeValidationError
|
| TypeValidationError
|
||||||
@ -297,6 +309,7 @@ export type SerializedAiSdkErrorUnion =
|
|||||||
| SerializedAiSdkNoSuchModelError
|
| SerializedAiSdkNoSuchModelError
|
||||||
| SerializedAiSdkNoSuchProviderError
|
| SerializedAiSdkNoSuchProviderError
|
||||||
| SerializedAiSdkNoSuchToolError
|
| SerializedAiSdkNoSuchToolError
|
||||||
|
| SerializedAiSdkProviderSpecificError
|
||||||
| SerializedAiSdkRetryError
|
| SerializedAiSdkRetryError
|
||||||
| SerializedAiSdkToolCallRepairError
|
| SerializedAiSdkToolCallRepairError
|
||||||
| SerializedAiSdkTypeValidationError
|
| SerializedAiSdkTypeValidationError
|
||||||
@ -317,6 +330,7 @@ export const isSerializedAiSdkErrorUnion = (error: SerializedError): error is Se
|
|||||||
isSerializedAiSdkNoSuchModelError(error) ||
|
isSerializedAiSdkNoSuchModelError(error) ||
|
||||||
isSerializedAiSdkNoSuchProviderError(error) ||
|
isSerializedAiSdkNoSuchProviderError(error) ||
|
||||||
isSerializedAiSdkNoSuchToolError(error) ||
|
isSerializedAiSdkNoSuchToolError(error) ||
|
||||||
|
isSerializedAiSdkProviderSpecificError(error) ||
|
||||||
isSerializedAiSdkRetryError(error) ||
|
isSerializedAiSdkRetryError(error) ||
|
||||||
isSerializedAiSdkToolCallRepairError(error) ||
|
isSerializedAiSdkToolCallRepairError(error) ||
|
||||||
isSerializedAiSdkTypeValidationError(error) ||
|
isSerializedAiSdkTypeValidationError(error) ||
|
||||||
|
|||||||
29
src/renderer/src/types/provider-specific-error.ts
Normal file
29
src/renderer/src/types/provider-specific-error.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { AISDKError } from 'ai'
|
||||||
|
|
||||||
|
const name = 'AI_ProviderSpecificError'
|
||||||
|
const marker = `vercel.ai.error.${name}`
|
||||||
|
const symbol = Symbol.for(marker)
|
||||||
|
|
||||||
|
export class ProviderSpecificError extends AISDKError {
|
||||||
|
// @ts-ignore
|
||||||
|
private readonly [symbol] = true // used in isInstance
|
||||||
|
|
||||||
|
readonly provider: string
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
message,
|
||||||
|
provider,
|
||||||
|
cause
|
||||||
|
}: {
|
||||||
|
message: string
|
||||||
|
provider: string
|
||||||
|
cause?: unknown
|
||||||
|
}) {
|
||||||
|
super({ name, message, cause })
|
||||||
|
this.provider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
static isInstance(error: unknown): error is ProviderSpecificError {
|
||||||
|
return AISDKError.hasMarker(error, marker)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -173,6 +173,7 @@ export const serializeError = (error: AiSdkErrorUnion): SerializedError => {
|
|||||||
? serializeInvalidToolInputError(error.originalError)
|
? serializeInvalidToolInputError(error.originalError)
|
||||||
: serializeNoSuchToolError(error.originalError)
|
: serializeNoSuchToolError(error.originalError)
|
||||||
if ('functionality' in error) serializedError.functionality = error.functionality
|
if ('functionality' in error) serializedError.functionality = error.functionality
|
||||||
|
if ('provider' in error) serializedError.provider = error.provider
|
||||||
|
|
||||||
return serializedError
|
return serializedError
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user