Skip to content

fix: reject DeepSeek WebSocket upgrades with 426 for a zero-retry HTTP fallback - #25

Merged
fish2lab merged 1 commit into
fish2lab:mainfrom
danielkuang666:fix/deepseek-ws-426-fast-fallback
Sep 20, 2026
Merged

fish2lab merged 1 commit into
fish2lab:mainfrom
danielkuang666:fix/deepseek-ws-426-fast-fallback

Conversation

@danielkuang666

Copy link
Copy Markdown
Contributor

背景

v1.2.1 release notes 的「未验收」里留了一个问题:桌面端是否每个 turn 按当前模型选传输——若先打 WS,DeepSeek 任务会先吃一次 1008 再回 HTTP。正常使用中已复现,代价是首轮约 10–15 秒的重连等待。

本机实测(macOS 桌面端 + CLI,ChatGPT OAuth,DSCodex v1.2.1)server.log 的典型序列:

deepseek websocket rejected /v1/responses   ← 首帧识别为 DeepSeek 后 close 1008
chatgpt websocket /v1/responses close 1006
…(客户端 Reconnecting 1/5 … 5/5,指数退避)
deepseek /v1/responses -> 200               ← 约 10–15s 后才真正开跑

根因

  • 客户端把「已建立的 WS 被服务端关闭」归类为可重试的流错误(stream_max_retries 默认 5,指数退避);
  • 但握手阶段的 HTTP 426 对应一条零重试的传输降级分支(WebsocketStreamOutcome::FallbackToHttp);
  • 模型名在握手时已经可得:ChatGPT 登录的客户端会带 x-codex-routing-hint: model=<slug>;tier=<tier>(该头已在 DSCodex 的转发白名单里)。

改动

  • handleResponsesUpgrade:握手读 x-codex-routing-hint,识别为 DeepSeek 时直接回 426,不建立 socket、不拨 upstream;
  • 提示缺失时(如 API-key 认证)保留原「首帧 close 1008」路径,行为不变;
  • GPT 请求不受影响,仍走 WS 透传;
  • 同步更新 AGENTS.md 规则 20 与 README.md / README.en.md 的边界描述(两条路径都写明)。

验证

  • 新增 3 个测试:
    1. routingHintModel 解析(含 tier、畸形值、缺失头);
    2. DeepSeek 提示 → HTTP/1.1 426,upstream 零拨号,路由器存活;
    3. GPT 提示 → 仍正常代理。
  • npm test:128 tests / 122 pass / 0 fail(6 skip 为 Windows 原生项);git diff --check 干净;适用于 CI 三平台。
  • 本机端到端实测:修复前首轮 Reconnecting 5/5 ≈ 10–15s;修复后握手即 426,端到端 4.5s、无 Reconnecting;GPT 的 WS 照常透传(chatgpt websocket ... open)。

兼容性

  • 无 x-codex-routing-hint 的客户端(API-key 认证、旧版本)行为完全不变;
  • 426 本就是客户端用于判定「WS 不可用」的既有路径,本次只是把判定提前到握手阶段,并顺带省掉一次 upstream 连接。

如果方向或细节与你们的偏好不一致(例如希望对 426 的触发条件更保守),我可以按反馈调整。

…P fallback

The client treats a DeepSeek model on an accepted socket as a retryable
stream error and burns 5 reconnect attempts (~10-15s of backoff) before
falling back to HTTP — the case the v1.2.1 release notes flagged as
unverified in normal DeepSeek use. The handshake already carries the model
in x-codex-routing-hint, and the client maps an upgrade-time HTTP 426
directly to its no-retry HTTP fallback (WebsocketStreamOutcome::
FallbackToHttp), so reject DeepSeek-hinted upgrades early. The first-frame
close-1008 path stays as the fallback for handshakes without the hint.
AGENTS.md rule 20 and both READMEs are updated.
@fish2lab
fish2lab merged commit b0225ab into fish2lab:main Sep 20, 2026
fish2lab pushed a commit that referenced this pull request Sep 20, 2026
Sub-agents on DeepSeek (#24), DeepSeek → GPT switch fixed again (#23),
instant 426 fallback for DeepSeek WebSocket handshakes (#25), voice
documented as client-owned since desktop 26.908 (#21 closed).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants