Repository navigation
v1.6.40 — 🔍 面板不再掩盖真实故障原因 / The Panel Stops Hiding Real Errors
v1.6.40 — 🔍 面板不再掩盖真实故障原因 / The Panel Stops Hiding Real Errors
来自 issue #11 追加反馈的排查成果。报告人贴出的截图里,账号显示 ACTIVE / Errors 0 / Models 4,聊天框却只有一句「无响应内容」—— 排查下来发现这四个数字没有一个能用。本版专治这一点。
From the follow-up investigation on issue #11. The reporter's screenshot showed ACTIVE / Errors 0 / Models 4 while the chat box said only "No response content" — it turned out none of those four numbers meant anything. This release fixes exactly that.
🇨🇳 简体中文
- 🔍 模型测试页不再把真实错误显示成「无响应内容」。 流式响应的 HTTP 状态在第一个字节发出时就定死为 200,之后的失败只能以流内 error 帧表达。后端一直在发(
{"error":{"message","type","code"}}),但前端只读chunk.choices[0].delta—— error 帧没有choices,取不到内容,于是落到「无响应内容」。
结果是503 会话已失效、529 账号池打满、500、400全部长得一模一样,用户和维护者都无从判断。现在显示网关错误 (503): <真实原因>;若错误发生在已经输出内容之后,已收到的内容会保留,错误追加在后面。错误文本走安全渲染,上游消息不会被当 HTML 执行。 - 🩺 账号卡片显示真实健康信息。 新增导出
is_healthy、consecutive_failures、last_success_at、last_error(含发生时间)。
此前面板上那个绿色ACTIVE徽章读的是账号池里的status枚举,不是客户端的健康位 —— 一个 cookie 已经彻底失效的账号照样显示绿色(status只在连续 3 次计数失败或 401/403 时才变EXPIRED)。现在「状态是 active 但客户端不健康」会给出醒目提示,并显示最近一次成功生成的时间。last_error只保留异常类型 + HTTP 状态码,刻意丢弃原文:原文里可能嵌着 Google 响应正文的前 200 字节(含登录态标识 / token / 账号邮箱),而模型不可用类异常的消息带客户端可控的 model 名,原样渲染会变成面板的存储型 XSS 入口。
- 📉 空响应单独计数,不再被当成成功悄悄咽下去。 上游返回 HTTP 200 但正文解析不出内容时,此前走
release(success=True)—— 错误计数不动、连续失败计数还被清零,账号永远不会因此被标记异常,面板上完全看不出来。- 单次空响应绝不降级账号(用户确实可能问出一次空回答),连续 3 次才计入错误计数;任何一次非空成功立即清零。
- 「只有思考过程没有正文」「只有图片没有文字」不算空。
- 🪵 启动时明说 cookie 轮换循环有没有起来:
Auto-refresh loop started / NOT started (原因)。此前无论跑没跑日志里都看不出来 —— 而它只在「启动时拿不到 token」或「手动重载 cookie」之后才启动,正常启动的网关根本不跑它。排查「cookie 过一阵就失效」类问题时,这一行是关键线索。
⚠️ 一处需要知悉的语义变化:连续空响应达阈值后,每多一次会给error_count+1。该字段同时喂着「用量统计」页的错误率曲线,所以一个卡在空响应里的账号会把那条曲线顶起来,尽管一次异常都没抛。看历史曲线时请留意。
🇭🇰 繁體中文
- 🔍 模型測試頁不再把真實錯誤顯示成「無回應內容」。 串流回應的 HTTP 狀態在第一個位元組送出時就定死為 200,之後的失敗只能以串流內 error 幀表達;後端一直在送,但前端只讀
chunk.choices[0].delta,error 幀沒有choices於是落到「無回應內容」——503/529/500/400全長一樣。現在顯示真實原因與狀態碼,已輸出的內容會保留,錯誤附在後面,且走安全繪製不會被當 HTML 執行。 - 🩺 帳號卡片顯示真實健康資訊(
is_healthy/連續失敗數/最近成功時間/最近錯誤含發生時間)。此前綠色ACTIVE徽章讀的是池裡的status列舉而非用戶端健康位,cookie 已失效的帳號照樣顯示綠色。last_error只留例外類型 + HTTP 狀態碼、刻意丟棄原文(原文可能含登入態標識/token/帳號信箱,且是儲存型 XSS 入口)。 - 📉 空回應單獨計數,不再被當成功嚥下去:連續 3 次才計入錯誤,單次絕不降級;「只有思考過程沒有正文」「只有圖片沒有文字」不算空。
- 🪵 啟動時明說 cookie 輪換迴圈有沒有起來 —— 它只在「啟動時拿不到 token」或「手動重載 cookie」後才啟動,正常啟動的閘道根本不跑它。
⚠️ 連續空回應達閾值後會累加error_count,該欄位同時餵著「用量統計」的錯誤率曲線。
🇺🇸 English
- 🔍 The model-test page no longer renders real errors as "No response content." With SSE the HTTP status is locked to
200the moment the first byte goes out, so any later failure can only be expressed as an in-stream error frame. The backend has always emitted one ({"error":{"message","type","code"}}), but the frontend only readchunk.choices[0].delta— an error frame has nochoices, so nothing was extracted and it fell through to "No response content."
The result:503 session expired,529 all accounts busy,500, and400all looked identical — neither users nor maintainers could tell them apart. It now showsGateway error (503): <real reason>. If the error arrives after content has already streamed, the received content is preserved and the error is appended. Error text is rendered safely; an upstream message can never execute as HTML. - 🩺 Account cards now expose real health information:
is_healthy,consecutive_failures,last_success_at, andlast_error(with its timestamp).
The greenACTIVEbadge reads the pool'sstatusenum, not the client's health flag — so an account whose cookie is completely dead still showed green (statusonly flips toEXPIREDafter 3 consecutive counted failures or a 401/403). A prominent warning now appears when an account isactivebut the client is unhealthy, alongside the time of its last successful generation.last_errorkeeps only the exception type and HTTP status, deliberately discarding the original text: that text can embed the first 200 bytes of Google's response body (login-state identifiers, tokens, the account email), and model-unavailable errors carry a client-controlled model name — rendering it verbatim would be a stored-XSS vector in the panel.
- 📉 Empty responses are counted instead of being silently swallowed as success. When upstream returns
200but no content can be parsed, the old path calledrelease(success=True)— the error counter never moved, the consecutive-failure counter was reset, and the account could never be flagged. Nothing was visible on the panel.- A single empty response never downgrades an account (a user genuinely can draw a blank answer); 3 in a row are required before it counts as an error, and any non-empty success resets the streak immediately.
- "Reasoning only, no answer text" and "images only, no text" are not treated as empty.
- 🪵 Startup now states whether the cookie-rotation loop actually started:
Auto-refresh loop started / NOT started (reason). Previously the logs said nothing either way — and the loop only starts after a failed token fetch at startup or a manual cookie reload, meaning a gateway that starts up normally never runs it at all. That single line is the key clue when diagnosing "the cookie dies after a while."
⚠️ One semantic change to be aware of: once the empty-response streak hits the threshold, each further empty response incrementserror_count. That field also feeds the error-rate chart on the Usage Statistics page, so an account stuck returning empties will push that curve up even though no exception was ever raised.
🇯🇵 日本語
- 🔍 モデルテストページが実際のエラーを「応答内容なし」と表示していた問題を修正。 SSE では最初のバイト送出時点で HTTP ステータスが 200 に確定するため、以降の失敗はストリーム内 error フレームでしか表現できません。バックエンドは送っていましたが、フロントは
chunk.choices[0].deltaしか見ておらず、error フレームにはchoicesが無いため「応答内容なし」に落ちていました。結果として503/529/500/400が全て同じ見た目に。 今後は実際の理由とステータスコードを表示し、既に出力済みの内容は保持してエラーを後ろに追記します。エラー文は安全に描画され、上流メッセージが HTML として実行されることはありません。 - 🩺 アカウントカードに実際の健全性情報を追加(
is_healthy/連続失敗数/最終成功時刻/秘匿化した直近エラーと発生時刻)。緑のACTIVEバッジはプールのstatus列挙であってクライアントの健全フラグではないため、cookie が完全に死んだアカウントも緑のままでした。last_errorは例外型と HTTP ステータスのみを残し原文を意図的に破棄します(原文には Google 応答本文の先頭 200 バイト=ログイン状態識別子・トークン・アカウントメールが含まれ得るため、またパネルの保存型 XSS 経路になるため)。 - 📉 空応答を成功として黙って飲み込まなくなりました。 従来は
release(success=True)に流れ、エラー計数は動かず連続失敗計数はリセットされ、パネルからは一切見えませんでした。単発では絶対に降格せず、連続 3 回で初めてエラー計上、非空の成功が一度あれば即座にリセット。「思考のみで本文なし」「画像のみで文字なし」は空とみなしません。 - 🪵 起動時に cookie ローテーションループが起動したかをログに明記。 このループは起動時にトークンを取得できなかった場合、または手動で cookie を再読み込みした後にのみ開始されるため、正常起動したゲートウェイでは一度も走りません。
⚠️ 閾値到達後の空応答はerror_countを加算します。この値は「使用状況統計」のエラー率グラフにも使われています。
🇰🇷 한국어
- 🔍 모델 테스트 페이지가 실제 오류를 "응답 내용 없음"으로 표시하던 문제 수정. SSE는 첫 바이트가 나가는 순간 HTTP 상태가 200으로 확정되므로 이후의 실패는 스트림 내 error 프레임으로만 표현됩니다. 백엔드는 계속 보내고 있었지만 프런트는
chunk.choices[0].delta만 읽었고, error 프레임에는choices가 없어 "응답 내용 없음"으로 떨어졌습니다. 그 결과503/529/500/400이 모두 똑같아 보였습니다. 이제 실제 원인과 상태 코드를 표시하며, 이미 출력된 내용이 있으면 보존한 채 뒤에 오류를 덧붙입니다. 오류 텍스트는 안전하게 렌더링되어 상류 메시지가 HTML로 실행되지 않습니다. - 🩺 계정 카드에 실제 상태 정보 추가(
is_healthy/연속 실패 수/마지막 성공 시각/비식별화된 최근 오류와 발생 시각). 녹색ACTIVE배지는 풀의status열거형이지 클라이언트 헬스 플래그가 아니어서, 쿠키가 완전히 죽은 계정도 녹색으로 보였습니다.last_error는 예외 타입과 HTTP 상태만 남기고 원문을 의도적으로 버립니다(원문에 Google 응답 본문 앞부분 — 로그인 상태 식별자·토큰·계정 이메일 — 이 포함될 수 있고, 패널의 저장형 XSS 경로가 되기 때문). - 📉 빈 응답을 성공으로 조용히 삼키지 않습니다. 기존에는
release(success=True)로 흘러 오류 계수는 그대로, 연속 실패 계수는 오히려 초기화되어 패널에서 전혀 보이지 않았습니다. 1회로는 절대 강등하지 않으며 연속 3회부터 오류로 집계하고, 비어 있지 않은 성공이 한 번이라도 있으면 즉시 초기화합니다. "사고 과정만 있고 본문 없음", "이미지만 있고 텍스트 없음"은 빈 것으로 보지 않습니다. - 🪵 시작 시 쿠키 로테이션 루프가 실제로 시작됐는지 로그에 명시. 이 루프는 시작 시 토큰을 얻지 못했거나 수동으로 쿠키를 다시 불러온 뒤에만 시작되므로, 정상적으로 시작된 게이트웨이에서는 한 번도 실행되지 않습니다.
⚠️ 임계값 도달 후의 빈 응답은error_count를 증가시키며, 이 값은 "사용량 통계" 오류율 그래프에도 사용됩니다.
升级 / Upgrade
cd gemini2api && docker compose pull && docker compose up -d