Skip to content

feat(qoder): in-process PAT bridge, reasoning stream, and in-process check-in - #313

Merged
caigee-cmd merged 8 commits into
caigee-cmd:mainfrom
favoroo:feat/qoder-in-process-bridge
Oct 19, 2026
Merged

caigee-cmd merged 8 commits into
caigee-cmd:mainfrom
favoroo:feat/qoder-in-process-bridge

Conversation

@favoroo

@favoroo favoroo commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Qoder accounts can now run fully in-process from a PAT with no per-account Node+WASM worker.

  • New qoder-pat-v1 credential format and in-process Bridge (internal/providers/qoder/bridge.go): PAT→jobToken exchange, proactive/forced refresh, /user/status + OpenAPI quota, /model/list catalog — via the qauth package ported from Qoder-2API-Go (see "Upstream attribution").
  • Check-in runs entirely in Go (campaigns list + claim) with a machine identity derived deterministically from the PAT (stable per account, unique across accounts on the same host); global region uses the OpenAPI bearer.
  • Fixes in-process chat losing the reasoning stream (chat_context fill), and catalog parsing from raw /model/list (key/display_name → CanonicalModelID).
  • PAT quota headline now sums subscription + add-on + organization resource package.

Upstream attribution

internal/providers/qoder/qauth is ported from EchoPing07/Qoder-2API-Go @ 65a5ffff122fdbc165275a631459b9801f9e097a (auth/auth.go), licensed MIT (Copyright (c) 2026 EchoPing). MIT permits redistribution under this project's MIT license with the notice retained; see internal/providers/qoder/qauth/NOTICE (same convention as internal/providers/devin/NOTICE).

Review follow-ups (2026-10-19)

  • TTFB failover + worker watchdog split out → fix(qoder): fail over on streaming first-byte timeout and watchdog stuck workers #315.
  • Desktop fingerprint helper removed: risk.go (executing the installed Qoder app's umid/runtime-info binary and sending the real hardware fingerprint upstream) is dropped. Check-in always uses the PAT-derived identity, so no third-party binary is executed server-side and accounts on one host never share a fingerprint.
  • AGENTS.md restored: the worker/src/compat.mjs pinning rule and the original architecture paragraph are back; the architecture-doc update will be proposed separately.
  • qauth tests added: 27 test functions covering custom base64, MD5 signing, AES/RSA, PAT→jobToken exchange and refresh (httptest), region selection, and the busy/auth/gateway error taxonomy.
  • Rebased onto latest main (includes fix(runtime): auto-refresh stale exhausted quota and back off probing #312); the stale "Stacked on" note is removed.

Tests

  • go build ./..., go vet ./..., go test ./..., git diff --check
  • (cd worker && npm test) — 84 passing
  • New Qoder bridge/check-in/chat-context tests and the qauth suite above.

@favoroo

favoroo commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up fix pushed (f1e7a97): Bridge.QuotaSnapshot projected only UserQuota into the store headline, leaving AddOnQuota / OrgResourcePackage as detail-only fields. The worker path (quota.go snapshot, issue #229) already sums the three buckets, so once a PAT account's plan bucket emptied, the console showed 0/0 while the official client showed the add-on balance (verified live: plan 0/0 + add-on 1,499/3,000 displayed as 0/0).

This commit mirrors the worker-path aggregation and the Exceeded override (an available add-on keeps Exceeded false), adds four regression tests (bridge_quota_test.go), and a bilingual changelog fragment. Live-verified on all three CN PAT accounts after rebuild: 1,499/3,000 and 600/600 ×2, matching the official client.

@caigee-cmd caigee-cmd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢这次很大的投入!PAT 全进程化、修复 reasoning 流丢失(chat_context)、从原始 /model/list 解析目录,以及新提交里把套餐、资源包、组织资源包三项加总显示额度,都是我们想要的方向,测试覆盖也比较充分。

由于范围较大,合并前需要先处理以下几点:

  1. Rebase:#312 已合入 main,请 rebase 到最新 main,或点 Update branch,让 diff 只剩本 PR 自己的改动。同时请更新 PR 描述里过时的 "Stacked on #4" 说明。
  2. 拆分:TTFB 30 秒故障转移和 worker 存活 watchdog(提交 556ce14)与 PAT bridge 无直接关系,请拆成单独的 PR,便于分别评审和回滚。
  3. 许可证与来源:qauth/qauth.go 和 risk.go 注明是从 qoder2api 逐字移植的。请在 PR 描述和文件头中写明上游仓库地址、对应的 commit 或版本以及许可证类型,确认许可证允许以本项目的协议再分发,并按要求补充 NOTICE 或版权声明。在这一点确认之前我们无法合并。
  4. 执行桌面端程序:risk.go 会查找并执行本机 Qoder 安装目录里的 umid/runtime-info 来获取设备指纹。在服务端执行第三方二进制、并把真实硬件指纹发给上游,存在安全隐患和被风控封号的风险,而且多账号共用同一台机器的指纹也可能导致账号被关联。请删除这部分,统一使用 PAT 派生的身份;如果确实需要保留,请改成默认关闭的显式配置项,并在文档中说明风险。
  5. AGENTS.md:本 PR 删除了 "Pin qodercli / qoderclicn hooks in worker/src/compat.mjs; fail loudly on mismatch" 这条规则并改写了架构说明。在 worker 仍然存在的情况下请先恢复原文,架构说明的更新请单独提出讨论,由维护者确认后再改。
  6. 测试:qauth 包目前没有任何单元测试。请至少覆盖签名与加解密、PAT 换 jobToken、刷新、区域选择和错误分类等核心路径,可以用 httptest 模拟上游。
  7. 另外,worker/NOTICE 新增段落引用的 docs/capture-notes.md (local-only) 不在仓库内,建议去掉这个引用或改成仓库内可访问的说明。

建议的拆分顺序:先合 PAT bridge 本体(含 qauth、许可证说明和测试,不含桌面 helper),再合 TTFB/watchdog,桌面指纹视讨论结果决定。辛苦了,有任何问题欢迎在这里讨论!

@favoroo
favoroo force-pushed the feat/qoder-in-process-bridge branch from 030d0cb to 4fb6d25 Compare October 19, 2026 04:42
@favoroo favoroo changed the title feat(qoder): in-process PAT bridge, reasoning stream, and genuine-fingerprint check-in feat(qoder): in-process PAT bridge, reasoning stream, and in-process check-in Oct 19, 2026
@favoroo

favoroo commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

@caigee-cmd 感谢详细 review!已逐条处理完毕:

  1. Rebase:已 rebase 到最新 main(含 fix(runtime): auto-refresh stale exhausted quota and back off probing #312),diff 现在只剩本 PR 的改动;描述里过时的 "Stacked on feat: per-account drop-system-prompt switch for WorkBuddy chat #4" 已删除。
  2. 拆分:TTFB 30s 故障转移 + worker 存活 watchdog 已拆到 fix(qoder): fail over on streaming first-byte timeout and watchdog stuck workers #315,本 PR 不再包含这部分。
  3. 许可证与来源:qauth 移植自 EchoPing07/Qoder-2API-Go @ 65a5fff(auth/auth.go),MIT 许可证(Copyright (c) 2026 EchoPing),与本项目 MIT 兼容。已按 internal/providers/devin/NOTICE 的惯例新增 internal/providers/qoder/qauth/NOTICE(上游 URL + commit + 版权声明 + MIT 全文),qauth.go 文件头也补了 URL/commit/许可证。
  4. 桌面指纹已删除:risk.go(执行本机 Qoder 安装目录的 umid/runtime-info、上报真实硬件指纹)已从本 PR 移除,签到统一使用 PAT 派生身份——不在服务端执行第三方二进制,多账号也不会共享同一指纹。
  5. AGENTS.md:已恢复原文(含 compat.mjs pin 规则),本 PR 现在对 AGENTS.md 零改动;架构说明的更新我会在合并后单独提 PR 讨论。
  6. qauth 测试:新增 qauth_test.go(27 个测试函数),覆盖自定义 base64 加解密往返、MD5 签名、AES/RSA、httptest 模拟的 PAT→jobToken 交换与刷新(成功 / busy 10605 / 401)、区域选择与 URL 拼接、busy/auth/gateway 错误分类、敏感字段打码、SSE reader(含流内 busy、截断流)。全部不触真实网络。
  7. worker/NOTICE:该文件是本 PR 拆分前由 watchdog 提交引入的,已随拆分移到 fix(qoder): fail over on streaming first-byte timeout and watchdog stuck workers #315,并在那里删除了对 local-only docs/capture-notes.md 的引用。

go build ./...、go test ./... 全绿。麻烦再看一眼,谢谢!

@caigee-cmd caigee-cmd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-ups — most of the earlier review items look good (TTFB/watchdog split to #315, risk.go removed, AGENTS.md untouched, qauth tests added, rebase done).

Still need a few small fixes before merge:

  1. login/pat fallback is too broad (internal/control/accounts.go). Any *OperationError from LoginPAT currently falls through to workerAdmin, including invalid_credential and credential_save_failed. That swallows real errors and forwards the PAT to the worker. Please fall back only on provider_unsupported; return other errors as-is.
  2. Attribution for check-in. checkin.go mirrors upstream auth/checkin.go (same function names/structure), but qauth/NOTICE and the qauth.go header only cite auth/auth.go. Please cite auth/checkin.go as well (and ideally include the MIT disclaimer paragraph).
  3. docs/ARCHITECTURE_SUMMARY.md. This PR rewrites the Qoder architecture section, while an earlier commit message said the architecture rewrite would be a separate PR. Please either restore the doc here and open a follow-up, or keep it and call that out clearly in the PR description so we can accept it on purpose.

Also please rebase onto latest main (it now includes #309 and #311).

@favoroo
favoroo force-pushed the feat/qoder-in-process-bridge branch from 7225a2e to fcb62c0 Compare October 19, 2026 07:09
@favoroo

favoroo commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

@caigee-cmd 感谢复查,三点都处理了(commit fcb62c0),并已 rebase 到最新 main(含 #309/#311):

  1. login/pat fallback 过宽 —— 现在只在 OperationError.Code == "provider_unsupported" 时才回退到 workerAdmin。invalid_credential、credential_save_failed 以及 store 错误等真实失败会原样返回,不再被吞掉后把 PAT 转发给 worker。新增两个测试:TestAccountsAdminLoginPATDoesNotSwallowRealErrors(invalid_credential / save failure 都不会走到 runtime.WorkerAdmin)和 TestAccountsAdminLoginPATFallsBackOnlyOnProviderUnsupported(无 importer 时才走 worker)。

  2. 签到归因 —— qauth/NOTICE 和 qauth.go 包头现在同时引用 auth/auth.go 与 auth/checkin.go,并说明 internal/providers/qoder/checkin.go 的 campaigns-list/claim 结构镜像自 auth/checkin.go;NOTICE 里补上了完整的 MIT 免责声明段落(THE SOFTWARE IS PROVIDED "AS IS"...)。

  3. docs/ARCHITECTURE_SUMMARY.md —— 已从本 PR 还原(回到 main 的版本)。之前 commit message 说的是架构文档另开 PR,为保持一致,这部分不在本 PR 里带。

go build ./...、go test ./...、(cd worker && npm test)(84 passing)全绿。

Copy link
Copy Markdown
Owner

上次提的几点都改好了,本地测试和 CI 都通过,感谢!

#315 刚合进 main,和这个 PR 有两处小冲突,麻烦 rebase 一下:

  • internal/providers/qoder/adapter.go 的 NewClient:保留 bridges:,再加上 firstByteTimeout: resolveFirstByteTimeout()
  • internal/providers/qoder/direct.go 的 Direct 结构体:保留 bridgeOf,再加上 ttfb time.Duration

CI 重新跑过后我就合。

…gerprint check-in

Migrate Qoder from the per-account Node+WASM worker to a fully in-process
Bridge (PAT -> jobToken, refresh, catalog, quota, chat signing), ported from
the qoder2api reference via the new internal/providers/qoder/qauth package.
The worker path is kept only as a legacy fallback for accounts still holding a
qoder-native-v1 CLI blob.

- qauth: port cosy wire protocol (RSA+AES+custom base64+MD5), jobToken
  exchange/refresh, /user/status, /model/list, OpenAPI quota, SSE reader,
  busy/auth/gateway error taxonomy.
- direct: populate chat_context (text/extra.modelConfig.is_reasoning/
  originalContent) and align source=1, version="3", session_type="qodercli"
  so the gateway emits the reasoning_content stream again.
- adapter: parse raw gateway /model/list entries (key + display_name) into
  ModelInfo so PAT accounts resolve a catalog instead of "unknown".
- catalog/probe: include in-process Qoder accounts in catalog merge and fetch
  their models when the adapter has no Prober.
- checkin: present the genuine desktop device fingerprint read from the
  installed app's umid/runtime-info helper (risk.go) instead of only the
  PAT-derived identity, and use the OpenAPI bearer on the global region. The
  campaign service only exposes the claimable daily-credit activity to the
  official fingerprint; helper-missing falls back to the derived identity.

Verified live: CN check-in now sees the credit campaign ("今日已签到"),
global no longer 401s, and qwen3.8-flash streams reasoning.
One bilingual fragment per PR, per changelog/unreleased/README.md.
The in-process Bridge.QuotaSnapshot projected only UserQuota into the
store headline, leaving AddOnQuota / OrgResourcePackage as detail-only
fields. The worker path (quota.go snapshot) already sums the three
buckets (issue caigee-cmd#229), so PAT-backed accounts drifted from the official
client once the plan bucket emptied: a plan 0/0 account with a
1499/3000 add-on balance displayed 0/0 in the console.

Mirror the worker-path aggregation and the Exceeded override (an
available add-on keeps Exceeded false), with regression tests.
…d per review

Per review on the PAT bridge PR:
- Remove risk.go: executing the desktop umid/runtime-info binary server-side
  and sending real hardware fingerprints upstream is a security and
  account-linkage risk; multi-account setups on one host would share one
  fingerprint. Check-in now always uses the PAT-derived identity.
- Restore the AGENTS.md qodercli pinning rule and the original architecture
  paragraph; the architecture rewrite will be proposed separately.
- Trim the consolidated changelog fragment accordingly (fingerprint bullets
  removed; TTFB/watchdog bullets moved to the split-out PR).
Add internal/providers/qoder/qauth/NOTICE with the upstream repository URL,
the ported commit (65a5fff), the copyright line, and the MIT license text,
following the existing internal/providers/devin/NOTICE convention. Update the
qauth.go package header and qoder2api mentions across code comments, the
changelog fragment, and docs/ARCHITECTURE_SUMMARY.md to name the upstream
project explicitly.
…d error taxonomy

Add qauth_test.go with 27 test functions: custom base64 round-trip, MD5
signing, AES-CBC/RSA primitives, region selection and URL builders,
PAT->jobToken exchange and refresh against httptest servers (success, busy
10605, 401 auth error), busy/auth/gateway error classification, error-body
redaction of sensitive keys, in-stream error detection, and the SSE line
reader (happy path, bare [DONE], in-stream busy, truncated stream).
…resh

TestModelsAPICachesCatalogForFiveMinutes asserted absolute catalog hit
counts, but the in-process boot RefreshAll now gives prober-less adapters a
one-shot model discovery pass, which can land mid-test on slow CI runners
(observed as hits=3 where 2 was expected). Wait for the hit counter to
quiesce after setup and assert relative deltas from that baseline.

Also gofmt catalog_source.go, bridge_quota_test.go, and qauth_test.go.
- login/pat: only fall back to the legacy worker path on provider_unsupported.
  Any other OperationError (invalid_credential, credential_save_failed) is a
  real failure and is returned as-is instead of being swallowed and forwarded
  to the worker with the PAT. Added tests for both paths.
- qauth NOTICE and package header now cite auth/checkin.go alongside auth/auth.go
  for the in-process check-in flow, and include the full MIT disclaimer
  paragraph.
- Restore docs/ARCHITECTURE_SUMMARY.md to main: the architecture rewrite was
  promised as a separate follow-up, so it should not ride in this PR.
@caigee-cmd
caigee-cmd force-pushed the feat/qoder-in-process-bridge branch from fcb62c0 to a2991dd Compare October 19, 2026 08:10

@caigee-cmd caigee-cmd left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

维护者已在 #315 合并后将本分支 rebase 到最新 main,按此前讨论解决了 adapter.go(bridges + firstByteTimeout)与 direct.go(bridgeOf + ttfb)两处冲突;本地 go test ./... 及 qoder/runtime/control -race 均通过,CI 6 项全绿。LGTM,合并。

@caigee-cmd
caigee-cmd merged commit 383745b into caigee-cmd:main Oct 19, 2026
6 checks 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