Skip to content

fix(responses): /v1/responses never sent captcha tokens — every start-plan request failed with 3007 - #38

Merged
TriDefender merged 2 commits into
TriDefender:masterfrom
LX2000WASD:fix/responses-captcha
Aug 31, 2026
Merged

TriDefender merged 2 commits into
TriDefender:masterfrom
LX2000WASD:fix/responses-captcha

Conversation

@LX2000WASD

Copy link
Copy Markdown
Contributor

问题

/v1/responses 完全没有接入验证码层。它在 handler.ts 传验证码头的位置传了 undefined:

// src/proxy/responses-handler.ts(修复前)
const upstreamHeaders = buildUpstreamHeaderPairs(..., opts.config.plan, undefined, undefined);
const upstreamReq     = buildUpstreamRequest(...,  opts.config.plan, undefined, undefined);

对比 handler.ts:159-172,那里会先 getCaptchaToken() 再注入 x-aliyun-captcha-verify-param / -region。

start-plan 下上游对每一个调用都做验证码校验,所以 /v1/responses 必然失败:

HTTP 400 {"error":{"type":"upstream_error",
          "message":"{\"code\":3007,\"msg\":\"captcha verify failed\"}"}}

而同一进程、同一凭证下 /v1/messages 和 /v1/chat/completions 完全正常 —— 因为 handler.ts 给它们铸了令牌。

这个组合(只在 start-plan、只在这一条路由)很容易被误判成求解器故障或指纹问题:本地求解成功、令牌校验通过、其他路由都好,唯独这条 400。实际上是少了调用点。grep -c captcha src/proxy/responses-handler.ts 修复前为 0。

改动

严格对齐 handler.ts 的既有模式,不引入新抽象:

  • 发前铸币:plan === "start-plan" 时取一枚池中令牌注入请求头,沿用同样的惰性 import("./captcha.js"),coding-plan 进程仍然不会加载 happy-dom 求解器。
  • 3007 重试一次:双通道检测,与 handler.ts 一致 —— 验证码响应头,以及 HTTP 400 + body 内 {"code":3007} 这个变体。被挑战的令牌已被消耗,所以重试必须带新令牌。
  • 提取 dispatch(pairs):首发与重试共用同一条 endpoint-routing + client-signing 链路,避免把那段逻辑抄第二遍。
  • 新增 captcha? 注入口:与文件里已有的 fetchImpl / endpointRouting / clientSigning 测试注入口并列,让测试无需联网即可覆盖这些路径。

验证

  • bun test:708 pass / 0 fail;bun x tsc --noEmit 干净。
  • 新增 5 个测试(注入假 captcha 模块,零网络、确定性):
    • start-plan 请求确实带上令牌
    • in-body {"code":3007} 触发重试,且重试携带的是新令牌(token-1 → token-2)
    • 响应头形式的挑战同样触发重试
    • 只重试一次,第二次仍失败则如实上抛 upstream_error(防止无限循环)
    • coding-plan 完全不碰验证码(不铸币、不加头)
  • 反向验证:把验证码头改回 undefined,其中 3 个测试立即失败。

环境:Bun 1.4.0 / linux-x64。此 PR 与 #37 相互独立(不同文件,无重叠),可单独合并。

LX2000WASD and others added 2 commits September 1, 2026 01:58
`/v1/responses` never touched the captcha layer. It passed `undefined`
where handler.ts passes captcha headers:

    buildUpstreamHeaderPairs(..., opts.config.plan, undefined, undefined)
    buildUpstreamRequest(..., opts.config.plan, undefined, undefined)

On start-plan the upstream gates every call behind an Aliyun captcha
token, so every /v1/responses request came back as

    HTTP 400 {"error":{"type":"upstream_error",
              "message":"{\"code\":3007,\"msg\":\"captcha verify failed\"}"}}

while /v1/messages and /v1/chat/completions worked, since handler.ts mints
a token for them. The failure is plan-specific and route-specific, which
made it look like a captcha-solver problem rather than a missing call site.

- Mint a pooled token before the upstream POST when plan is start-plan,
  mirroring handler.ts (same lazy captcha import so coding-plan processes
  never load the solver).
- Retry once on an explicit challenge, detected both ways handler.ts does:
  the captcha response header and the in-body `{"code":3007}` variant on
  HTTP 400. The challenged token is already spent, so the retry carries a
  freshly pooled one.
- Extract the send into `dispatch(pairs)` so the first attempt and the
  retry share one routing/signing path.
- Add a `captcha` option alongside the existing fetchImpl/endpointRouting/
  clientSigning test seams, so tests exercise the paths without network.

Tests: token attached on start-plan, retry on in-body 3007, retry on
header challenge, exactly one retry (no loop) with the upstream error
surfaced afterwards, and coding-plan untouched.
@TriDefender

Copy link
Copy Markdown
Owner

LGTM
小瑕疵(不阻塞):重试时 dispatch 的网络错误会误标为 503 captcha_solver_failed 而非 502 upstream_unreachable(handler.ts 用内层 .catch 区分了这两者);整个检测块 gated 在 !upstreamResp.ok 上,而 handler.ts 对“200 + 挑战头”也会重试——网关实际只用 403/400 变体,属理论边缘。

@TriDefender
TriDefender merged commit 5ea9581 into TriDefender:master Aug 31, 2026
1 check passed
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