feat(errors): classify provider errors and show a hint the user can act on

- lib/llm-errors.ts sorts an error into about a dozen kinds (key
  rejected, no access, unknown model, no credit, rate limited, context
  too long, no image input, no tool calls, output cut off, provider down,
  cannot connect, timeout): first texts that name the cause precisely,
  then the HTTP status code, then general texts. It unwraps RetryError and
  hides keys and Bearer tokens in the provider's message
- The chat route uses it for errors before the stream and, through
  toUIMessageStreamResponse's onError, for errors in the stream. Errors
  of the model's own tool call stay as they are: the same text goes back
  to the model so it can fix the call
- The chat shows the hint in the user's language, then the provider's
  message; a rejected key, missing access or unknown model adds an "Open
  model settings" button. The Test button shows the same hints
- Fixes: our message "API key is required when using a custom base URL"
  was replaced by "Authentication failed" because it contains "key"; a
  provider's "Rate limit exceeded" opened this site's quota toast; an
  error body like {"error": ...} was shown as raw JSON; the Test button
  matched "401" in the message, where providers rarely put it
- Remove the string matching fallbacks in the chat panel
This commit is contained in:
dayuan.jiang
2026-10-04 13:49:34 +09:00
parent 99890e9e37
commit ab1a58f999
12 changed files with 487 additions and 142 deletions
+29
View File
@@ -148,6 +148,8 @@ interface SessionMetadata {
interface ChatMessageDisplayProps {
messages: UIMessage[]
// Shown on an error that a model setting can fix (bad key, unknown model)
onOpenModelConfig?: () => void
setInput: (input: string) => void
setFiles: (files: File[]) => void
processedToolCallsRef: MutableRefObject<Set<string>>
@@ -171,6 +173,7 @@ interface ChatMessageDisplayProps {
export function ChatMessageDisplay({
messages,
onOpenModelConfig,
setInput,
setFiles,
processedToolCallsRef,
@@ -1193,6 +1196,32 @@ export function ChatMessageDisplay({
return null
},
)}
{message.role ===
"system" &&
(
message.metadata as
| {
openModelConfig?: boolean
}
| undefined
)
?.openModelConfig &&
onOpenModelConfig && (
<button
type="button"
onClick={
onOpenModelConfig
}
className="mt-2 text-xs font-medium underline underline-offset-2 hover:opacity-80"
>
{
dict
.errors
.llm
.openModelSettings
}
</button>
)}
</div>
)
},