Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bd4ac5d
fix(proxy): 识别 200+HTML 假200并触发故障转移
tesgth032 Feb 11, 2026
d3f9427
fix(utils): 收紧 HTML 文档识别避免误判
tesgth032 Feb 11, 2026
6204733
fix(proxy): 非流式假200补齐强信号 JSON error 检测
tesgth032 Feb 11, 2026
edb3dac
fix(utils): 假200检测兼容 BOM
tesgth032 Feb 11, 2026
de8c498
perf(proxy): 降低非流式嗅探读取上限
tesgth032 Feb 11, 2026
a8f6b44
fix(proxy): 客户端隐藏 FAKE_200_* 内部码
tesgth032 Feb 11, 2026
380981a
fix(logs): 补齐 endpoint_pool_exhausted/404 错因展示
tesgth032 Feb 11, 2026
005fad3
fix(proxy): 非流式 JSON 假200检测覆盖 Content-Length
tesgth032 Feb 11, 2026
3a9df87
chore: format code (fix-issue-749-fake-200-html-detection-005fad3)
github-actions[bot] Feb 11, 2026
218c2b0
fix(i18n): 修正 ru 端点池耗尽文案
tesgth032 Feb 11, 2026
20c0d49
test(formatter): 补齐 resource_not_found 组合场景覆盖
tesgth032 Feb 11, 2026
0aebbf1
fix(proxy): FAKE_200 客户端提示附带脱敏片段
tesgth032 Feb 11, 2026
62fcf63
fix: 改进 FAKE_200 错误原因提示
tesgth032 Feb 11, 2026
f43e4dd
merge: 同步上游 dev
tesgth032 Feb 11, 2026
e9e2f04
fix(proxy): verboseProviderError 回传假200原文
tesgth032 Feb 11, 2026
1c5ef19
fix(proxy): 强化 Content-Length 校验与假200片段防泄露
tesgth032 Feb 11, 2026
fd3cd80
docs(proxy): 说明非流式假200检测上限
tesgth032 Feb 11, 2026
7cd494b
docs(settings): 补充 verboseProviderError 安全提示
tesgth032 Feb 11, 2026
b56b790
fix(proxy): verboseProviderError rawBody 基础脱敏
tesgth032 Feb 12, 2026
bb7649a
chore: format code (fix-issue-749-fake-200-html-detection-b56b790)
github-actions[bot] Feb 12, 2026
5794c6d
docs(settings): 说明 verboseProviderError 基础脱敏
tesgth032 Feb 12, 2026
62f24a8
merge: 同步上游 dev
tesgth032 Feb 12, 2026
63523bc
fix(proxy/logs): 假200 推断状态码并显著标记
tesgth032 Feb 12, 2026
8b39a0f
fix(i18n): 回退 verboseProviderErrorDesc 原始文案
tesgth032 Feb 12, 2026
59c6430
fix(stream): 404 资源不存在不计入熔断
tesgth032 Feb 12, 2026
25978b0
Merge branch 'refactor/fake-200-error-logs' into fix/issue-749-fake-2…
ding113 Feb 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions messages/en/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@
},
"errorMessage": "Error Message",
"fake200ForwardedNotice": "Note: For streaming requests, this failure may be detected only after the stream ends; the response content may already have been forwarded to the client.",
"fake200DetectedReason": "Detected reason: {reason}",
"fake200Reasons": {
"emptyBody": "Empty response body",
"htmlBody": "HTML document returned (likely an error page)",
"jsonErrorNonEmpty": "JSON has a non-empty `error` field",
"jsonErrorMessageNonEmpty": "JSON has a non-empty `error.message`",
"jsonMessageKeywordMatch": "JSON `message` contains the word \"error\" (heuristic)",
"unknown": "Response body indicates an error"
},
"statusCodeInferredBadge": "Inferred",
"statusCodeInferredTooltip": "This status code is inferred from response body content (e.g., fake 200) and may differ from the upstream HTTP status.",
"statusCodeInferredSuffix": "(inferred)",
"filteredProviders": "Filtered Providers",
"providerChain": {
"title": "Provider Decision Chain Timeline",
Expand Down
5 changes: 5 additions & 0 deletions messages/en/provider-chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"candidate": "{name}({probability}%)",
"requestChain": "Request Chain:",
"systemError": "System Error",
"resourceNotFound": "Resource Not Found (404)",
"concurrentLimit": "Concurrent Limit",
"http2Fallback": "HTTP/2 Fallback",
"clientError": "Client Error",
Expand All @@ -46,6 +47,7 @@
"retry_success": "Retry Success",
"retry_failed": "Retry Failed",
"system_error": "System Error",
"resource_not_found": "Resource Not Found (404)",
"client_error_non_retryable": "Client Error",
"concurrent_limit_failed": "Concurrent Limit",
"http2_fallback": "HTTP/2 Fallback",
Expand Down Expand Up @@ -128,11 +130,13 @@
"candidateInfo": " • {name}: weight={weight} cost={cost} probability={probability}%",
"selected": "✓ Selected: {provider}",
"requestFailed": "Request Failed (Attempt {attempt})",
"resourceNotFoundFailed": "Resource Not Found (404) (Attempt {attempt})",
"attemptNumber": "Attempt {number}",
"firstAttempt": "First Attempt",
"nthAttempt": "Attempt {attempt}",
"provider": "Provider: {provider}",
"statusCode": "Status Code: {code}",
"statusCodeInferred": "Status Code (inferred): {code}",
"error": "Error: {error}",
"requestDuration": "Request Duration: {duration}ms",
"requestDurationSeconds": "Request Duration: {duration}s",
Expand All @@ -158,6 +162,7 @@
"meaning": "Meaning",
"notCountedInCircuit": "This error is not counted in provider circuit breaker",
"systemErrorNote": "Note: This error is not counted in provider circuit breaker",
"resourceNotFoundNote": "Note: This error is not counted in the circuit breaker and will trigger failover after retries are exhausted.",
"reselection": "Reselecting Provider",
"reselect": "Reselecting Provider",
"excluded": "Excluded: {providers}",
Expand Down
12 changes: 12 additions & 0 deletions messages/ja/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@
},
"errorMessage": "エラーメッセージ",
"fake200ForwardedNotice": "注意:ストリーミング要求では、失敗判定がストリーム終了後になる場合があります。応答内容は既にクライアントへ転送されている可能性があります。",
"fake200DetectedReason": "検出理由:{reason}",
"fake200Reasons": {
"emptyBody": "レスポンス本文が空です",
"htmlBody": "HTML ドキュメントが返されました (エラーページの可能性)",
"jsonErrorNonEmpty": "JSON の `error` フィールドが空ではありません",
"jsonErrorMessageNonEmpty": "JSON の `error.message` が空ではありません",
"jsonMessageKeywordMatch": "JSON の `message` に \"error\" が含まれています (ヒューリスティック)",
"unknown": "レスポンス本文がエラーを示しています"
},
"statusCodeInferredBadge": "推定",
"statusCodeInferredTooltip": "このステータスコードは応答本文の内容(例: fake 200)から推定されており、上流の HTTP ステータスと異なる場合があります。",
"statusCodeInferredSuffix": "(推定)",
"filteredProviders": "フィルタされたプロバイダー",
"providerChain": {
"title": "プロバイダー決定チェーンタイムライン",
Expand Down
5 changes: 5 additions & 0 deletions messages/ja/provider-chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"candidate": "{name}({probability}%)",
"requestChain": "リクエストチェーン:",
"systemError": "システムエラー",
"resourceNotFound": "リソースが見つかりません(404)",
"concurrentLimit": "同時実行制限",
"http2Fallback": "HTTP/2 フォールバック",
"clientError": "クライアントエラー",
Expand All @@ -46,6 +47,7 @@
"retry_success": "リトライ成功",
"retry_failed": "リトライ失敗",
"system_error": "システムエラー",
"resource_not_found": "リソースが見つかりません(404)",
"client_error_non_retryable": "クライアントエラー",
"concurrent_limit_failed": "同時実行制限",
"http2_fallback": "HTTP/2 フォールバック",
Expand Down Expand Up @@ -128,11 +130,13 @@
"candidateInfo": " • {name}: 重み={weight} コスト={cost} 確率={probability}%",
"selected": "✓ 選択: {provider}",
"requestFailed": "リクエスト失敗(試行{attempt})",
"resourceNotFoundFailed": "リソースが見つかりません(404)(試行{attempt})",
"attemptNumber": "試行 {number}",
"firstAttempt": "初回試行",
"nthAttempt": "試行{attempt}",
"provider": "プロバイダー: {provider}",
"statusCode": "ステータスコード: {code}",
"statusCodeInferred": "ステータスコード(推定): {code}",
"error": "エラー: {error}",
"requestDuration": "リクエスト時間: {duration}ms",
"requestDurationSeconds": "リクエスト時間: {duration}s",
Expand All @@ -158,6 +162,7 @@
"meaning": "意味",
"notCountedInCircuit": "このエラーはプロバイダーサーキットブレーカーにカウントされません",
"systemErrorNote": "注記:このエラーはプロバイダーサーキットブレーカーにカウントされません",
"resourceNotFoundNote": "注記:このエラーはサーキットブレーカーにカウントされず、リトライ枯渇後にフェイルオーバーします。",
"reselection": "プロバイダー再選択",
"reselect": "プロバイダー再選択",
"excluded": "除外済み: {providers}",
Expand Down
12 changes: 12 additions & 0 deletions messages/ru/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@
},
"errorMessage": "Сообщение об ошибке",
"fake200ForwardedNotice": "Примечание: для потоковых запросов эта ошибка может быть обнаружена только после завершения потока; содержимое ответа могло уже быть передано клиенту.",
"fake200DetectedReason": "Причина обнаружения: {reason}",
"fake200Reasons": {
"emptyBody": "Пустое тело ответа",
"htmlBody": "Получен HTML-документ (возможно, страница ошибки)",
"jsonErrorNonEmpty": "В JSON непустое поле `error`",
"jsonErrorMessageNonEmpty": "В JSON непустое `error.message`",
"jsonMessageKeywordMatch": "В JSON `message` содержит слово \"error\" (эвристика)",
"unknown": "Тело ответа указывает на ошибку"
},
"statusCodeInferredBadge": "Предположено",
"statusCodeInferredTooltip": "Этот код состояния выведен по содержимому тела ответа (например, fake 200) и может отличаться от HTTP-кода апстрима.",
"statusCodeInferredSuffix": "(предп.)",
"filteredProviders": "Отфильтрованные поставщики",
"providerChain": {
"title": "Хронология цепочки решений поставщика",
Expand Down
5 changes: 5 additions & 0 deletions messages/ru/provider-chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"candidate": "{name}({probability}%)",
"requestChain": "Цепочка запросов:",
"systemError": "Системная ошибка",
"resourceNotFound": "Ресурс не найден (404)",
"concurrentLimit": "Лимит параллельных запросов",
"http2Fallback": "Откат HTTP/2",
"clientError": "Ошибка клиента",
Expand All @@ -46,6 +47,7 @@
"retry_success": "Повтор успешен",
"retry_failed": "Повтор не удался",
"system_error": "Системная ошибка",
"resource_not_found": "Ресурс не найден (404)",
"client_error_non_retryable": "Ошибка клиента",
"concurrent_limit_failed": "Лимит параллельных запросов",
"http2_fallback": "Откат HTTP/2",
Expand Down Expand Up @@ -128,11 +130,13 @@
"candidateInfo": " • {name}: вес={weight} стоимость={cost} вероятность={probability}%",
"selected": "✓ Выбрано: {provider}",
"requestFailed": "Запрос не выполнен (Попытка {attempt})",
"resourceNotFoundFailed": "Ресурс не найден (404) (Попытка {attempt})",
"attemptNumber": "Попытка {number}",
"firstAttempt": "Первая попытка",
"nthAttempt": "Попытка {attempt}",
"provider": "Провайдер: {provider}",
"statusCode": "Код состояния: {code}",
"statusCodeInferred": "Код состояния (выведено): {code}",
"error": "Ошибка: {error}",
"requestDuration": "Длительность запроса: {duration}мс",
"requestDurationSeconds": "Длительность запроса: {duration}с",
Expand All @@ -158,6 +162,7 @@
"meaning": "Значение",
"notCountedInCircuit": "Эта ошибка не учитывается в автомате защиты провайдера",
"systemErrorNote": "Примечание: Эта ошибка не учитывается в автомате защиты провайдера",
"resourceNotFoundNote": "Примечание: Эта ошибка не учитывается в автомате защиты; после исчерпания повторов произойдёт переключение.",
"reselection": "Повторный выбор провайдера",
"reselect": "Повторный выбор провайдера",
"excluded": "Исключено: {providers}",
Expand Down
12 changes: 12 additions & 0 deletions messages/zh-CN/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@
},
"errorMessage": "错误信息",
"fake200ForwardedNotice": "提示:对于流式请求,该失败可能在流结束后才被识别;响应内容可能已原样透传给客户端。",
"fake200DetectedReason": "检测原因:{reason}",
"fake200Reasons": {
"emptyBody": "响应体为空",
"htmlBody": "返回了 HTML 文档(可能是错误页)",
"jsonErrorNonEmpty": "JSON 顶层 error 字段非空",
"jsonErrorMessageNonEmpty": "JSON 中 error.message 非空",
"jsonMessageKeywordMatch": "JSON message 字段包含 \"error\"(启发式)",
"unknown": "响应体内容指示错误"
},
"statusCodeInferredBadge": "推测",
"statusCodeInferredTooltip": "该状态码根据响应体内容推断(例如假200),可能与上游真实 HTTP 状态码不同。",
"statusCodeInferredSuffix": "(推测)",
"filteredProviders": "被过滤的供应商",
"providerChain": {
"title": "供应商决策链时间线",
Expand Down
5 changes: 5 additions & 0 deletions messages/zh-CN/provider-chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"candidate": "{name}({probability}%)",
"requestChain": "请求链路:",
"systemError": "系统错误",
"resourceNotFound": "资源不存在(404)",
"concurrentLimit": "并发限制",
"http2Fallback": "HTTP/2 回退",
"clientError": "客户端错误",
Expand All @@ -46,6 +47,7 @@
"retry_success": "重试成功",
"retry_failed": "重试失败",
"system_error": "系统错误",
"resource_not_found": "资源不存在(404)",
"client_error_non_retryable": "客户端错误",
"concurrent_limit_failed": "并发限制",
"http2_fallback": "HTTP/2 回退",
Expand Down Expand Up @@ -128,11 +130,13 @@
"candidateInfo": " • {name}: 权重={weight} 成本={cost} 概率={probability}%",
"selected": "✓ 选择: {provider}",
"requestFailed": "请求失败(第 {attempt} 次尝试)",
"resourceNotFoundFailed": "资源不存在(404,第 {attempt} 次尝试)",
"attemptNumber": "第 {number} 次",
"firstAttempt": "首次尝试",
"nthAttempt": "第 {attempt} 次尝试",
"provider": "供应商: {provider}",
"statusCode": "状态码: {code}",
"statusCodeInferred": "状态码(推测): {code}",
"error": "错误: {error}",
"requestDuration": "请求耗时: {duration}ms",
"requestDurationSeconds": "请求耗时: {duration}s",
Expand All @@ -158,6 +162,7 @@
"meaning": "含义",
"notCountedInCircuit": "此错误不计入供应商熔断器",
"systemErrorNote": "说明:此错误不计入供应商熔断器",
"resourceNotFoundNote": "说明:该错误不计入熔断器;重试耗尽后将触发故障转移。",
"reselection": "重新选择供应商",
"reselect": "重新选择供应商",
"excluded": "已排除: {providers}",
Expand Down
12 changes: 12 additions & 0 deletions messages/zh-TW/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@
},
"errorMessage": "錯誤訊息",
"fake200ForwardedNotice": "提示:對於串流請求,此失敗可能在串流結束後才被識別;回應內容可能已原樣透傳給用戶端。",
"fake200DetectedReason": "檢測原因:{reason}",
"fake200Reasons": {
"emptyBody": "回應本文為空",
"htmlBody": "回傳了 HTML 文件(可能是錯誤頁)",
"jsonErrorNonEmpty": "JSON 頂層 error 欄位非空",
"jsonErrorMessageNonEmpty": "JSON 中 error.message 非空",
"jsonMessageKeywordMatch": "JSON message 欄位包含 \"error\"(啟發式)",
"unknown": "回應本文內容顯示錯誤"
},
"statusCodeInferredBadge": "推測",
"statusCodeInferredTooltip": "此狀態碼係根據回應內容推測(例如假200),可能與上游真實 HTTP 狀態碼不同。",
"statusCodeInferredSuffix": "(推測)",
"filteredProviders": "被過濾的供應商",
"providerChain": {
"title": "供應商決策鏈時間軸",
Expand Down
5 changes: 5 additions & 0 deletions messages/zh-TW/provider-chain.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"candidate": "{name}({probability}%)",
"requestChain": "請求鏈路:",
"systemError": "系統錯誤",
"resourceNotFound": "資源不存在(404)",
"concurrentLimit": "並發限制",
"http2Fallback": "HTTP/2 回退",
"clientError": "客戶端錯誤",
Expand All @@ -46,6 +47,7 @@
"retry_success": "重試成功",
"retry_failed": "重試失敗",
"system_error": "系統錯誤",
"resource_not_found": "資源不存在(404)",
"client_error_non_retryable": "客戶端錯誤",
"concurrent_limit_failed": "並發限制",
"http2_fallback": "HTTP/2 回退",
Expand Down Expand Up @@ -128,11 +130,13 @@
"candidateInfo": " • {name}: 權重={weight} 成本={cost} 概率={probability}%",
"selected": "✓ 選擇: {provider}",
"requestFailed": "請求失敗(第 {attempt} 次嘗試)",
"resourceNotFoundFailed": "資源不存在(404,第 {attempt} 次嘗試)",
"attemptNumber": "第 {number} 次",
"firstAttempt": "首次嘗試",
"nthAttempt": "第 {attempt} 次嘗試",
"provider": "供應商: {provider}",
"statusCode": "狀態碼: {code}",
"statusCodeInferred": "狀態碼(推測): {code}",
"error": "錯誤: {error}",
"requestDuration": "請求耗時: {duration}ms",
"requestDurationSeconds": "請求耗時: {duration}s",
Expand All @@ -158,6 +162,7 @@
"meaning": "含義",
"notCountedInCircuit": "此錯誤不計入供應商熔斷器",
"systemErrorNote": "說明:此錯誤不計入供應商熔斷器",
"resourceNotFoundNote": "說明:該錯誤不計入熔斷器;重試耗盡後將觸發故障轉移。",
"reselection": "重新選擇供應商",
"reselect": "重新選擇供應商",
"excluded": "已排除: {providers}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,15 @@ const messages = {
},
errorMessage: "Error message",
fake200ForwardedNotice: "Note: detected after stream end; payload may have been forwarded",
fake200DetectedReason: "Detected reason: {reason}",
fake200Reasons: {
emptyBody: "Empty response body",
htmlBody: "HTML document returned",
jsonErrorNonEmpty: "JSON has non-empty error field",
jsonErrorMessageNonEmpty: "JSON has non-empty error.message",
jsonMessageKeywordMatch: 'JSON message contains "error"',
unknown: "Response body indicates an error",
},
viewDetails: "View details",
filteredProviders: "Filtered providers",
providerChain: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ function getRequestStatus(item: ProviderChainItem): StepStatus {
if (
item.reason === "retry_failed" ||
item.reason === "system_error" ||
item.reason === "resource_not_found" ||
item.reason === "client_error_non_retryable" ||
item.reason === "endpoint_pool_exhausted" ||
item.reason === "concurrent_limit_failed"
) {
return "failure";
Expand Down Expand Up @@ -464,10 +466,10 @@ export function LogicTraceTab({
subtitle={
isSessionReuse
? item.statusCode
? `HTTP ${item.statusCode}`
? `HTTP ${item.statusCode}${item.statusCodeInferred ? ` ${t("statusCodeInferredSuffix")}` : ""}`
: item.name
: item.statusCode
? `HTTP ${item.statusCode}`
? `HTTP ${item.statusCode}${item.statusCodeInferred ? ` ${t("statusCodeInferredSuffix")}` : ""}`
: item.reason
? tChain(`reasons.${item.reason}`)
: undefined
Expand Down
Loading