diff --git a/.env.example b/.env.example index 14193bd05..485ad52db 100644 --- a/.env.example +++ b/.env.example @@ -64,6 +64,10 @@ STORE_SESSION_MESSAGES=false # 会话消息存储模式(默认:fa # - false:存储请求/响应体但对 message 内容脱敏 [REDACTED] # - true:原样存储 message 内容(注意隐私和存储空间影响) # 警告:启用后会增加 Redis/DB 存储空间,且包含敏感信息 +STORE_SESSION_RESPONSE_BODY=true # 是否在 Redis 中存储会话响应体(默认:true) + # - true:存储(SSE/JSON),用于调试/定位问题(Redis 临时缓存) + # - false:不存储响应体(注意:不影响本次请求处理;仅影响后续查看 response body) + # 说明:该开关不影响内部统计读取响应体(tokens/费用统计、SSE 假 200 检测仍会进行) # 熔断器配置 # 功能说明:控制网络错误是否计入熔断器失败计数 diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 3ef7670e2..d30cd49d8 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -243,6 +243,7 @@ "billingRedirected": "billing: redirected" }, "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.", "filteredProviders": "Filtered Providers", "providerChain": { "title": "Provider Decision Chain Timeline", @@ -493,7 +494,7 @@ "providers": "Providers", "models": "Models", "noDetailedData": "No detailed data available", - "storageTip": "No detailed data found. To view request details, please check if the environment variable STORE_SESSION_MESSAGES is set to true. Note: Enabling this increases Redis memory usage and may include sensitive information.", + "storageTip": "No detailed data found. Possible reasons: Redis is disabled/unavailable (REDIS_URL + ENABLE_RATE_LIMIT=true), the data expired (SESSION_TTL, default 300s), or response body storage is disabled (STORE_SESSION_RESPONSE_BODY=false, affects response body only). To store unredacted messages, set STORE_SESSION_MESSAGES=true.", "clientInfo": "Client Info", "requestHeaders": "Request Headers", "requestBody": "Request Body", @@ -571,7 +572,7 @@ "fetchFailed": "Fetch Failed", "unknownError": "Unknown Error", "storageNotEnabled": "Not Stored", - "storageNotEnabledHint": "Tip: Set environment variable STORE_SESSION_MESSAGES=true to enable messages storage" + "storageNotEnabledHint": "Tip: Check REDIS_URL and ENABLE_RATE_LIMIT=true (session details cache). To store unredacted messages, set STORE_SESSION_MESSAGES=true." }, "errors": { "copyFailed": "Copy Failed" diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index 0ac8af2fd..c73664cf5 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -243,6 +243,7 @@ "billingRedirected": "課金: 実際" }, "errorMessage": "エラーメッセージ", + "fake200ForwardedNotice": "注意:ストリーミング要求では、失敗判定がストリーム終了後になる場合があります。応答内容は既にクライアントへ転送されている可能性があります。", "filteredProviders": "フィルタされたプロバイダー", "providerChain": { "title": "プロバイダー決定チェーンタイムライン", @@ -493,7 +494,7 @@ "providers": "プロバイダー", "models": "モデル", "noDetailedData": "詳細データなし", - "storageTip": "詳細データが見つかりません。リクエストの詳細を表示するには、環境変数 STORE_SESSION_MESSAGES が true に設定されているか確認してください。注意:有効にすると Redis のメモリ使用量が増加し、機密情報が含まれる可能性があります。", + "storageTip": "詳細データが見つかりません。原因の例:Redis が未設定/利用不可 (REDIS_URL + ENABLE_RATE_LIMIT=true)、データの期限切れ (SESSION_TTL、既定 300 秒)、または応答本文の保存を無効化 (STORE_SESSION_RESPONSE_BODY=false、応答本文のみ)。未マスクの messages を保存するには STORE_SESSION_MESSAGES=true を設定してください。", "clientInfo": "クライアント情報", "requestHeaders": "リクエストヘッダー", "requestBody": "リクエストボディ", @@ -571,7 +572,7 @@ "fetchFailed": "取得失敗", "unknownError": "不明なエラー", "storageNotEnabled": "未保存", - "storageNotEnabledHint": "ヒント: メッセージの保存を有効にするには、環境変数 STORE_SESSION_MESSAGES=true を設定してください" + "storageNotEnabledHint": "ヒント: REDIS_URL と ENABLE_RATE_LIMIT=true を確認してください (セッション詳細キャッシュ)。未マスクの messages を保存するには STORE_SESSION_MESSAGES=true を設定してください。" }, "errors": { "copyFailed": "コピー失敗" diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index a23506cfe..0a0dc47fc 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -243,6 +243,7 @@ "billingRedirected": "оплата: факт." }, "errorMessage": "Сообщение об ошибке", + "fake200ForwardedNotice": "Примечание: для потоковых запросов эта ошибка может быть обнаружена только после завершения потока; содержимое ответа могло уже быть передано клиенту.", "filteredProviders": "Отфильтрованные поставщики", "providerChain": { "title": "Хронология цепочки решений поставщика", @@ -493,7 +494,7 @@ "providers": "Поставщики", "models": "Модели", "noDetailedData": "Подробные данные отсутствуют", - "storageTip": "Подробные данные не найдены. Чтобы просмотреть детали запроса, проверьте, установлена ли переменная окружения STORE_SESSION_MESSAGES в значение true. Примечание: включение увеличит использование памяти Redis и может содержать конфиденциальную информацию.", + "storageTip": "Подробные данные не найдены. Возможные причины: Redis отключен/недоступен (REDIS_URL + ENABLE_RATE_LIMIT=true), данные истекли (SESSION_TTL, по умолчанию 300с), или сохранение тела ответа отключено (STORE_SESSION_RESPONSE_BODY=false, влияет только на тело ответа). Чтобы сохранять сообщения без маскировки, установите STORE_SESSION_MESSAGES=true.", "clientInfo": "Информация о клиенте", "requestHeaders": "Заголовки запроса", "requestBody": "Тело запроса", @@ -571,7 +572,7 @@ "fetchFailed": "Не удалось получить", "unknownError": "Неизвестная ошибка", "storageNotEnabled": "Не сохранено", - "storageNotEnabledHint": "Подсказка: установите переменную окружения STORE_SESSION_MESSAGES=true, чтобы включить сохранение сообщений" + "storageNotEnabledHint": "Подсказка: проверьте REDIS_URL и ENABLE_RATE_LIMIT=true (кэш деталей сессии). Чтобы сохранять сообщения без маскировки, установите STORE_SESSION_MESSAGES=true." }, "errors": { "copyFailed": "Не удалось скопировать" diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index 79381ffac..21dbf23c9 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -243,6 +243,7 @@ "billingRedirected": "计费: 实际" }, "errorMessage": "错误信息", + "fake200ForwardedNotice": "提示:对于流式请求,该失败可能在流结束后才被识别;响应内容可能已原样透传给客户端。", "filteredProviders": "被过滤的供应商", "providerChain": { "title": "供应商决策链时间线", @@ -493,7 +494,7 @@ "providers": "供应商", "models": "模型", "noDetailedData": "暂无详细数据", - "storageTip": "未找到详细数据。如需查看请求详情,请检查环境变量 STORE_SESSION_MESSAGES 是否已设置为 true。注意:启用后会增加 Redis 内存使用,且可能包含敏感信息。", + "storageTip": "未找到详细数据。可能原因:Redis 未配置/不可用(REDIS_URL + ENABLE_RATE_LIMIT=true)、数据已过期(SESSION_TTL,默认 300 秒),或已禁用响应体存储(STORE_SESSION_RESPONSE_BODY=false,仅影响响应体)。如需保存未脱敏 messages,请设置 STORE_SESSION_MESSAGES=true。", "clientInfo": "客户端信息", "requestHeaders": "请求头", "requestBody": "请求体", @@ -571,7 +572,7 @@ "fetchFailed": "获取失败", "unknownError": "未知错误", "storageNotEnabled": "未存储", - "storageNotEnabledHint": "提示:请设置环境变量 STORE_SESSION_MESSAGES=true 以启用 messages 存储" + "storageNotEnabledHint": "提示:请检查 REDIS_URL 与 ENABLE_RATE_LIMIT=true(用于会话详情缓存);如需保存未脱敏 messages,请设置 STORE_SESSION_MESSAGES=true。" }, "errors": { "copyFailed": "复制失败" diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index fcf8179ae..75eda170c 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -243,6 +243,7 @@ "billingRedirected": "計費: 實際" }, "errorMessage": "錯誤訊息", + "fake200ForwardedNotice": "提示:對於串流請求,此失敗可能在串流結束後才被識別;回應內容可能已原樣透傳給用戶端。", "filteredProviders": "被過濾的供應商", "providerChain": { "title": "供應商決策鏈時間軸", @@ -493,7 +494,7 @@ "providers": "供應商", "models": "Model", "noDetailedData": "暫無詳細資料", - "storageTip": "未找到詳細資料。如需查看請求詳情,請檢查環境變數 STORE_SESSION_MESSAGES 是否已設定為 true。注意:啟用後會增加 Redis 記憶體使用,且可能包含敏感資訊。", + "storageTip": "未找到詳細資料。可能原因:Redis 未設定/不可用(REDIS_URL + ENABLE_RATE_LIMIT=true)、資料已過期(SESSION_TTL,預設 300 秒),或已停用回應本文儲存(STORE_SESSION_RESPONSE_BODY=false,僅影響回應本文)。如需儲存未脫敏的 messages,請設定 STORE_SESSION_MESSAGES=true。", "clientInfo": "用戶端資訊", "requestHeaders": "請求頭", "requestBody": "請求體", @@ -571,7 +572,7 @@ "fetchFailed": "取得失敗", "unknownError": "未知錯誤", "storageNotEnabled": "未儲存", - "storageNotEnabledHint": "提示:請設定環境變數 STORE_SESSION_MESSAGES=true 以啟用訊息儲存" + "storageNotEnabledHint": "提示:請檢查 REDIS_URL 與 ENABLE_RATE_LIMIT=true(用於 Session 詳情快取);如需儲存未脫敏的 messages,請設定 STORE_SESSION_MESSAGES=true。" }, "errors": { "copyFailed": "複製失敗" diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 index a0a50d286..d7f1e41de 100644 --- a/scripts/deploy.ps1 +++ b/scripts/deploy.ps1 @@ -496,6 +496,7 @@ ENABLE_RATE_LIMIT=true # Session Configuration SESSION_TTL=300 STORE_SESSION_MESSAGES=false +STORE_SESSION_RESPONSE_BODY=true # Cookie Security ENABLE_SECURE_COOKIES=$secureCookies diff --git a/scripts/deploy.sh b/scripts/deploy.sh index ea39c350f..51e457a8e 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -578,6 +578,7 @@ ENABLE_RATE_LIMIT=true # Session Configuration SESSION_TTL=300 STORE_SESSION_MESSAGES=false +STORE_SESSION_RESPONSE_BODY=true # Cookie Security ENABLE_SECURE_COOKIES=${secure_cookies} diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx index fd1f878b8..9cb749975 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog.test.tsx @@ -252,6 +252,7 @@ const messages = { default: "No error", }, errorMessage: "Error message", + fake200ForwardedNotice: "Note: detected after stream end; payload may have been forwarded", viewDetails: "View details", filteredProviders: "Filtered providers", providerChain: { @@ -325,6 +326,21 @@ function parseHtml(html: string) { } describe("error-details-dialog layout", () => { + test("renders fake-200 forwarded notice when errorMessage is a FAKE_200_* code", () => { + const html = renderWithIntl( + + ); + + expect(html).toContain("FAKE_200_EMPTY_BODY"); + expect(html).toContain("Note: detected after stream end; payload may have been forwarded"); + }); + test("renders special settings section when specialSettings exists", () => { const html = renderWithIntl( 0 ? JSON.stringify(specialSettings, null, 2) : null; + const isFake200PostStreamFailure = + typeof errorMessage === "string" && errorMessage.startsWith("FAKE_200_"); return (
@@ -423,6 +426,13 @@ export function SummaryTab({

{errorMessage.length > 200 ? `${errorMessage.slice(0, 200)}...` : errorMessage}

+ {/* 注意:假 200 检测发生在 SSE 流式结束后;此时内容已可能透传给客户端,因此需要提示用户避免误解。 */} + {isFake200PostStreamFailure && ( +
+
+ )} {errorMessage.length > 200 && onViewLogicTrace && (