Skip to content

feat(qwenwork): add QwenWork in-process provider (global + CN regions) - #301

Merged
caigee-cmd merged 13 commits into
caigee-cmd:mainfrom
shenyushun:qwenwork-provider
Oct 19, 2026
Merged

caigee-cmd merged 13 commits into
caigee-cmd:mainfrom
shenyushun:qwenwork-provider

Conversation

@shenyushun

Copy link
Copy Markdown

Summary

  • New qwenwork provider: an in-process adapter for QwenWork (千问办公 / QwenWork international), with two regions under one family — global (gateway.qwenwork.ai, default) and cn (gateway.qwenwork.cn), following the Qoder/WorkBuddy region pattern. No changes to the Qoder provider or worker.
  • Login: single-use device authorization with PKCE (S256), polled in-process; refresh-token rotation is saved automatically, single-flighted per account, and runs on a detached context so a cancelled request cannot lose the rotated pair.
  • Chat: COSY-signed plain-JSON agent_chat_generation (same RSA key as Qoder, qwork scene, signed path without /algo); the wrapped upstream SSE is relayed as OpenAI chunks with tool-call delta merging, per-request chunk ids, and classified in-stream errors (code/kind/status carried for the gateway classifier).
  • Error taxonomy: quota code 14018 → quota; "Model is not available for this user" → model_not_available (cools only the model, never the account); body-level 4xx codes classify even inside 200-class frames; an error-only first frame fails over before any client byte is relayed.
  • Models: live qwork catalog with per-gateway cache; quota from account-context (credits, plan).
  • Console: QwenWork International / 千问办公 国内版 account types with single-use-link hints, a geometric provider mark (no trademark art), and the qwenwork/ model prefix in executor prepare and console model settings.
  • Verification: unit tests cover login, per-region headers, COSY signature, stream translation, aggregation, classification, models, and quota. Live-account acceptance on the international region: streaming/non-streaming chat, multi-turn, /v1/messages, /v1/responses, quota display, and wrong-model requests that do not cool the account. The CN region follows the desktop bundle configuration and is not yet verified with a real CN account (noted in the changelog and console hint).

Test plan

  • go test ./...
  • go vet ./...
  • go test -race ./...
  • cd worker && npm test
  • cd frontend && npm run build && npm run lint
  • Added changelog/unreleased/qwenwork-provider.md (bilingual, matching bullets)
  • No tokens, auth blobs, raw captures, or host details in the diff

whale added 10 commits October 15, 2026 22:22
Device-flow login with PKCE, lazy token refresh with rotation, COSY-signed
chat (plain JSON agent_chat_generation), wrapped-SSE translation to OpenAI
chunks, live qwork model catalog, account-context quota, error taxonomy
incl. quota code 14018 and model_not_available, and credential import for
both the canonical and qwenwork2api shapes. Regions: global (qwenwork.ai)
and cn (qwenwork.cn).
Adds the QwenWork descriptor (global/cn regions, in-process, OAuth) to the
provider registry, registers the adapter in app assembly, and admits the
qwenwork/ model prefix in executor prepare and console model settings.
Architecture guard now lists qwenwork as a concrete provider.
Provider family label, geometric QwenworkMark (no trademark art), global/cn
account types with single-use-link hints, qwenwork-oauth-v1 import default,
and the qwenwork/ prefix hint. Static bundle resynced.
…frame errors

Final-review fixes: concurrent near-expiry callers now share one refresh
round-trip (the rotated refresh token is single-use; a second exchange
reads session-dead and disables the account). The non-stream aggregate
merges fragmented tool-call deltas by index instead of keeping the last
fragment. Quota code 14018 and the model-unavailable text classify even
inside 200-class wrapper frames, with quota frames mapping to 402.
…d zhipu

First streamed chunk now carries delta.role so strict OpenAI SDKs recognize
the assistant turn. A trailing error frame wins over partial content in both
paths (matching the JS reference): non-stream returns the classified error,
stream keeps the error object as the terminal event with no synthetic
finish_reason after it. Architecture guard's concrete-provider list now
includes zhipu.
…mports

Refresh: the single-flight leader now reloads the stored credential before
exchanging (a stale caller snapshot replaying a consumed refresh token read
as session-dead and disabled the account), and the exchange runs on a
detached context with its own timeout so a cancelled request cannot fail the
waiters or lose the rotated pair.

Streams: an error-only first frame now fails over before any client byte is
relayed, and in-stream error chunks carry code/kind/status so the gateway
classifier keeps the provider verdict (quota 14018, auth) instead of
re-guessing unavailable. Chunks get a per-request id and created timestamp;
content and an error in the same frame are relayed content-first.

Imports: credentials without a refresh token skip the exchange instead of
being marked login_required; a missing uid is filled from userinfo on Probe
and Refresh; millisecond expiries normalize to seconds.

Also: model catalog keyed per gateway, max_tokens clamped to the catalog
output limit, the chat path reads the account row once, oversized
non-stream bodies fail loudly (upstream_body_too_large), quota code 14018
matches only on the code field, and abandoned login states are dropped on
StartLogin.
# Conflicts:
#	internal/webui/static/assets/TrafficChart-204RRZzP.js
#	internal/webui/static/assets/TrafficChart-BzsJ8hqy.js
#	internal/webui/static/assets/TrafficChart-Cu4wMr7T.js
#	internal/webui/static/assets/index-B2E3UrDA.js
#	internal/webui/static/assets/index-CmISzNrs.css
#	internal/webui/static/assets/index-CmKR3T8N.css
#	internal/webui/static/assets/index-DUre_bfC.css
#	internal/webui/static/index.html

@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 QwenWork provider — the source looks solid (go build / go test / frontend lint pass on a merge tree with main's static assets), and the new -cn/-gl/-auto prefixes from #311 will work with qwenwork/ once this lands.

Before merge:

  1. Rebase onto latest main and regenerate internal/webui/static/* with a frontend build. Conflicts today are only in those built files; every source file auto-merges.
  2. Re-run CI on the rebased head.
  3. Please add one short note about the "JS reference" / qwenwork2api mentioned in sse.go — which repo it came from and what license it uses (or confirm it's an original rewrite with no ported code).

After that this should be ready.

caigee-cmd and others added 2 commits October 19, 2026 16:18
# Conflicts:
#	internal/webui/static/assets/TrafficChart-BzsJ8hqy.js
#	internal/webui/static/assets/TrafficChart-DOugBEQu.js
#	internal/webui/static/assets/TrafficChart-Dw17gNRz.js
#	internal/webui/static/assets/index-BRuJ6yEl.js
#	internal/webui/static/index.html
@caigee-cmd

Copy link
Copy Markdown
Owner

我已合并最新 main 并重新生成了静态文件,CI 全部通过。

现在只剩来源和许可证的问题:代码里提到了 "JS reference" 和 qwenwork2api,请说明具体是哪个仓库(比如是否是 nostalgia296/qwenwork2api-makers)、使用什么许可证,以及代码是移植过来的还是只参考了行为。确认之后就可以合并。

@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.

来源说明已补上(独立实现,对照未声明许可证的 qwenwork2api-makers 协议行为),CI 通过,可以合。

@caigee-cmd
caigee-cmd merged commit d330b2b 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