Skip to content

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

Open
shenyushun wants to merge 9 commits into
caigee-cmd:mainfrom
shenyushun:qwenwork-provider
Open

shenyushun wants to merge 9 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 9 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.
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.

1 participant