Skip to content

Commit fc8fdb7

Browse files
committed
feat: add Qoder CN accounts with region-isolated CLI runtimes
Add Qoder China as provider=qoder + region=cn, pinning qoderclicn 1.1.27 and keeping Global/CN failover, credentials, and HOME directories separate.
1 parent a0a58ae commit fc8fdb7

31 files changed

Lines changed: 863 additions & 239 deletions

‎AGENTS.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ Keep writing-code docs in these files only:
1111
| `docs/PLAN.md` | Current milestone checklist. |
1212
| `docs/PROVIDERS.md` | Future account-provider design. Not a current milestone. |
1313

14-
Do not add new `TODO.md`, `NOTES.md`, or extra plan files. The only extra design doc is `docs/PROVIDERS.md` (WorkBuddy / future account types). User-facing install stays in `README.md` / `README_ZH.md`. Local protocol facts stay in ignored `docs/capture-notes.md`. Host ops stay gitignored in `docs/PRIVATE_DEPLOYMENT.md`.
14+
Do not add new `TODO.md`, `NOTES.md`, or extra plan files. The only extra design doc is `docs/PROVIDERS.md` (WorkBuddy / Qoder CN / future account types). User-facing install stays in `README.md` / `README_ZH.md`. Local protocol facts stay in ignored `docs/capture-notes.md`. Host ops stay gitignored in `docs/PRIVATE_DEPLOYMENT.md`.
1515

1616
## Do
1717

1818
- Pull latest `main` and merge it into the current branch before starting any feature work (skip only when already on up-to-date `main`)
1919
- Keep architecture: auth / endpoint / executor / translate / api
2020
- Prefer direct HTTP/SSE to Qoder cloud APIs
21-
- Pin qodercli hooks in `worker/src/compat.mjs`; fail loudly on mismatch
21+
- Pin qodercli / qoderclicn hooks in `worker/src/compat.mjs`; fail loudly on mismatch. Qoder CN is `provider=qoder` + `region=cn`, not a new family
2222
- Console UI: React + Tailwind v4 + **HeroUI only** for components
2323
- Follow `docs/DESIGN.md` (taste v1 adapted for this console)
2424
- Keep iterating Qoder login, usage, and account routing. Borrow scheduling ideas from [sub2api](https://github.com/Wei-Shaw/sub2api), not its commercial gateway
@@ -32,4 +32,4 @@ Do not add new `TODO.md`, `NOTES.md`, or extra plan files. The only extra design
3232
- Leave console `/api/*` or worker `/admin/*` unauthenticated
3333
- Copy sub2api billing, Redis slots, multi-tenant API keys, or session-hash-for-profit
3434
- Add a new component library, purple AI chrome, centered generic login cards, or emoji in UI copy
35-
- Start Cursor / Anthropic / WorkBuddy until the current Qoder milestone in `docs/PLAN.md` is done
35+
- Start Cursor / Anthropic until the current Qoder milestone in `docs/PLAN.md` is done. Qoder CN is that milestone (`provider=qoder` + `region=cn`); do not spawn a full `qoderclicn` per request

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`.
77

88
### English
99

10+
- Add Qoder CN accounts as `provider=qoder` + `region=cn`, using pinned `@qodercn-ai/qoderclicn@1.1.27` and `.qoder-cn`
11+
- Keep Qoder failover inside the same region so Global 429s do not land on CN
1012
- Send CodeBuddy CLI 2.139.0 channel headers on WorkBuddy chat, with CN and Global Origin/host kept separate
1113

1214
### 中文
1315

16+
- 支持添加 Qoder 国内版账号(`provider=qoder`,`region=cn`),使用 pinned `@qodercn-ai/qoderclicn@1.1.27` 和 `.qoder-cn`
17+
- Qoder 故障切换限制在同一 region,国际版 429 不会打到国内版
1418
- WorkBuddy 聊天补齐 CodeBuddy CLI 2.139.0 通道头,国内版 / 国际版 Origin 与 host 仍分开
1519

1620
## 0.2.10 - 2026-08-26

‎README.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ X-Qoder-Account: acc_...
164164
- Route requests across multiple Qoder accounts with failover
165165
- Keep Qoder login state available without starting a full CLI Agent per request
166166

167-
Qoder is the only supported upstream at this time. CLI2API is a local gateway; it does not provide accounts, quotas, or an official Qoder API service.
167+
Supported account types are Qoder Global, Qoder CN, and WorkBuddy. CLI2API is a local gateway; it does not provide accounts, quotas, or an official API service.
168168

169169
## How it works
170170

@@ -179,7 +179,7 @@ Each enabled account gets its own Node process and runtime directory so Qoder WA
179179

180180
## Features
181181

182-
- Browser Device Flow OAuth, PAT, and `qoder-native-v1` credential import/export
182+
- Browser Device Flow OAuth, PAT, and `qoder-native-v1` credential import/export for Qoder Global and Qoder CN
183183
- OpenAI-compatible `GET /v1/models`
184184
- Streaming and non-streaming responses
185185
- Tool calls and `reasoning_content`
@@ -196,6 +196,8 @@ Each enabled account gets its own Node process and runtime directory so Qoder WA
196196
| `QODER_RUNTIME_DIR` | `/run/cli2api` | Ephemeral per-account Qoder runtime homes |
197197
| `QODER_MAX_INFLIGHT` | `4` | Maximum concurrent requests per account |
198198
| `QODER_WORKER_BASE_PORT` | `32100` | Internal worker port range |
199+
| `QODERCLI_JS` | image default | Pinned Qoder Global CLI bundle |
200+
| `QODERCNCLI_JS` | image default | Pinned Qoder CN CLI bundle |
199201
| `UPDATE_GITHUB_TOKEN` | empty | Optional GitHub token for release checks |
200202
| `UPDATE_AGENT_URL` | empty | Docker Desktop host updater URL, written by the installer |
201203
| `UPDATE_AGENT_TOKEN` | empty | Docker Desktop updater token, written by the installer |

‎README_ZH.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ X-Qoder-Account: acc_...
164164
- 需要在多个 Qoder 账号之间自动路由和故障切换
165165
- 想保留 Qoder 登录能力,同时避免每个请求启动完整 CLI Agent
166166

167-
目前上游只支持 Qoder。CLI2API 是本地代理,不提供账号、额度或官方 API 服务。
167+
目前支持的账号类型是 Qoder 国际版、Qoder 国内版和 WorkBuddy。CLI2API 是本地代理,不提供账号、额度或官方 API 服务。
168168

169169
## 工作方式
170170

@@ -179,7 +179,7 @@ OpenAI 客户端
179179

180180
## 支持的功能
181181

182-
- 浏览器 Device Flow OAuth、PAT、`qoder-native-v1` 凭证导入/导出
182+
- Qoder 国际版 / 国内版的浏览器 Device Flow OAuth、PAT、`qoder-native-v1` 凭证导入/导出
183183
- OpenAI 兼容的 `GET /v1/models`
184184
- 流式和非流式响应
185185
- Tool calls 与 `reasoning_content`
@@ -196,6 +196,8 @@ OpenAI 客户端
196196
| `QODER_RUNTIME_DIR` | `/run/cli2api` | 临时的账号 Qoder 运行目录 |
197197
| `QODER_MAX_INFLIGHT` | `4` | 单账号最大并发请求数 |
198198
| `QODER_WORKER_BASE_PORT` | `32100` | 内部 worker 端口起点 |
199+
| `QODERCLI_JS` | 镜像默认值 | 固定的 Qoder 国际版 CLI bundle |
200+
| `QODERCNCLI_JS` | 镜像默认值 | 固定的 Qoder 国内版 CLI bundle |
199201
| `UPDATE_GITHUB_TOKEN` | 空 | 可选的 GitHub Release 查询 Token |
200202
| `UPDATE_AGENT_URL` | 空 | Docker Desktop 本机 updater 地址,由安装器写入 |
201203
| `UPDATE_AGENT_TOKEN` | 空 | Docker Desktop updater 令牌,由安装器写入 |

‎deploy/Dockerfile‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ FROM node:20-bookworm-slim
2525
RUN apt-get update \
2626
&& apt-get install -y --no-install-recommends ca-certificates curl tini \
2727
&& rm -rf /var/lib/apt/lists/* \
28-
&& npm install -g @qoder-ai/qodercli@1.1.27
28+
&& npm install -g @qoder-ai/qodercli@1.1.27 @qodercn-ai/qoderclicn@1.1.27
2929
WORKDIR /app
3030
COPY --from=build /out/qoder-api-proxy /app/qoder-api-proxy
3131
COPY --from=build /out/cli2api-updater /app/cli2api-updater
@@ -39,6 +39,7 @@ ENV HOST=0.0.0.0 \
3939
QODER_NODE_BINARY=/usr/local/bin/node \
4040
QODER_WORKER_DAEMON=/app/worker/daemon.mjs \
4141
QODERCLI_JS=/usr/local/lib/node_modules/@qoder-ai/qodercli/bundle/qodercli.js \
42+
QODERCNCLI_JS=/usr/local/lib/node_modules/@qodercn-ai/qoderclicn/bundle/qoderclicn.js \
4243
PLAIN_TEMPLATE_PATH=/app/worker/last-plain.sample.json \
4344
QODER_WORKER_BASE_PORT=32100
4445
EXPOSE 3010

‎docs/DESIGN.md‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,15 @@ Client (OpenAI SDK / Codex / CherryStudio)
275275
-> qoder-api-proxy (:3010) # one container
276276
-> Go control plane # auth, SQLite, routing, console
277277
-> Node daemon per account # isolated HOME + hot QoderContext
278-
-> https://api1.qoder.sh/.../agent_chat_generation?Encode=1
278+
-> region=global: https://api1.qoder.sh/.../agent_chat_generation?Encode=1
279+
-> region=cn: https://gateway.qoder.com.cn/... (WASM-encoded URL)
279280
```
280281

281-
Worker pins `@qoder-ai/qodercli@1.1.27`. Needle mismatch exits loudly.
282+
Worker pins `@qoder-ai/qodercli@1.1.27` for `qoder/global`. Qoder CN
283+
(`provider=qoder`, `region=cn`) is the same daemon with a second pin,
284+
`@qodercn-ai/qoderclicn@1.1.27`, and HOME `.qoder-cn`. Needle mismatch
285+
exits loudly. See `docs/PROVIDERS.md` 「Qoder CN」; do not spawn a full
286+
CLI per request and do not invent a `qodercn` family.
282287

283288
The request path that builds plaintext payloads, calls Qoder WASM encode, forwards
284289
HTTP/SSE, parses tools/reasoning, and resolves usage remains unchanged. The account
@@ -322,9 +327,10 @@ When qodercli changes, update the Qoder adapter and its version-aware compatibil
322327
tests first; do not duplicate the change in both public protocol handlers. Keep
323328
`/v1/chat/completions` stable while `/v1/messages` is added as a separate ingress.
324329

325-
Additional account providers (WorkBuddy, later Cursor) are not part of this Qoder
326-
milestone. If a second upstream is added, it must plug into the account registry and
327-
executor as a provider adapter, not a second Node daemon. See `docs/PROVIDERS.md`.
330+
Qoder CN is still the Qoder family (`provider=qoder`, `region=cn`): same Node
331+
daemon, second pinned CLI. WorkBuddy is the in-process adapter. Later Cursor
332+
must plug into the account registry as its own provider, not a third copy of
333+
the Qoder worker. See `docs/PROVIDERS.md`.
328334

329335
`/v1/messages` should be implemented as a native Anthropic boundary, not as
330336
Anthropic -> OpenAI -> Qoder string rewriting. Borrow sub2api's reversible tool
@@ -358,10 +364,10 @@ credential record is authoritative; the runtime files are derived working copies
358364

359365
Supported account onboarding:
360366

361-
- Qoder browser device-flow OAuth
362-
- Qoder PAT login
367+
- Qoder browser device-flow OAuth (Global and CN; CN host is qoder.com.cn)
368+
- Qoder PAT login (CN token UI: `https://qoder.com.cn/account/integrations`)
363369
- `qoder-native-v1` JSON import containing the encrypted `.auth/user` blob and its
364-
matching `machine_id`
370+
matching `machine_id` (Global lives under `.qoder`, CN under `.qoder-cn`)
365371

366372
Arbitrary `access_token` / `refresh_token` JSON is not supported. Qoder credentials
367373
also depend on private user material, organization data, encryption keys, and device

‎docs/PLAN.md‎

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CLI2API Plan
22

3-
last-updated: 2026-08-25
3+
last-updated: 2026-08-26
44

55
Qoder-first OpenAI-compatible proxy. Cursor and other CLIs wait until the current Qoder milestone is done.
66

@@ -28,7 +28,7 @@ Do not:
2828
- Spawn a full `qodercli` agent per request
2929
- Public exposure without the generated API key
3030
- Commercial multi-user resale of one login
31-
- Cursor / other CLI providers in this milestone
31+
- Cursor / other non-Qoder CLI providers in this milestone
3232
- Commit host IPs, passwords, auth blobs, or `docs/PRIVATE_DEPLOYMENT.md`
3333

3434
## Status
@@ -40,6 +40,8 @@ Do not:
4040
| E | Open-source clone-and-run; `v0.1.0` at `eaf81ad` |
4141
| F | Error taxonomy, account failover, console sign-in, Accounts login |
4242
| G | Replaced by Phase H SQLite account control plane |
43+
| H–K | SQLite control plane, protocol notes, logs, quota display |
44+
| L | Qoder CN (`qoder` + `region=cn`) — planned, see below |
4345

4446
Typical small-chat latency is ~1-2s after warmup, versus ~10s+ for spawn-CLI wrappers.
4547

@@ -168,15 +170,78 @@ no scheduling, billing, or persistence changes.
168170
- [x] Account card renders quota progress bar + add-on line (danger at exceeded, warning at ≥80%)
169171
- [x] Worker compat, Go manager, and frontend coverage
170172

173+
## Phase L — Qoder CN (China mainland)
174+
175+
Goal: add Qoder China as `provider=qoder` + `region=cn`. Reuse the existing
176+
child-process worker and `qoder-native-v1` credentials. Pin a second CLI,
177+
`@qodercn-ai/qoderclicn@1.1.27`, instead of a new protocol adapter.
178+
179+
Design facts and locked product decisions: `docs/PROVIDERS.md` section
180+
「Qoder CN(中国大陆版)」. Do not spawn a full `qoderclicn` per request.
181+
Do not invent a `qodercn` provider family. Do not upgrade the 1.1.27 pin.
182+
183+
### L0 descriptor
184+
185+
- [x] Register Qoder region `cn` on the existing Qoder descriptor
186+
- [x] Accept `POST /api/accounts` with `provider=qoder, region=cn`
187+
- [x] Keep historical empty region as `global`
188+
- [x] Flip `TestStoreRejectsUnknownProviderAndRegion` so `qoder+cn` is valid
189+
- [x] Do not merge L0 alone: an enabled CN account would still spawn the global CLI
190+
191+
### L1 worker CLI filename
192+
193+
- [x] Hook `qoderclicn.js` in `rewrite-loader.mjs` (same needles as 1.1.27 global; `includes("qodercli.js")` does **not** match `qoderclicn.js`)
194+
- [x] Resolve default bundle paths for both packages in `daemon.mjs`
195+
- [x] Choose `hotEndpoint` fallback from worker-only `QODER_SITE` or the loaded filename (`api1.qoder.sh` vs `gateway.qoder.com.cn`)
196+
- [x] Do not use `QODERCLI_SITE` to flip CN/Global; that env does not change compile-time `Xi`
197+
198+
### L2 manager spawn
199+
200+
- [x] Add `QODERCNCLI_JS` / `QoderCNCLIPath`; missing CN path fails spawn, never falls back to global CLI
201+
- [x] Keep `HOME={runtime}`. Pass CLI-readable dirs: `QODER_CONFIG_DIR={home}/.qoder` or `QODERCN_CONFIG_DIR={home}/.qoder-cn`. Do **not** rely on `QODER_HOME` — 1.1.27 CLI ignores it
202+
- [x] `materializeHome` / `SyncCredential` take region and read/write `.qoder` or `.qoder-cn`
203+
- [x] Export `qoder-native-v1` includes `provider` + `region`; import without region stays global for old bundles
204+
- [x] Unit test: CN account env points at `qoderclicn.js` + `.qoder-cn`; global still uses `qodercli.js` + `.qoder`
205+
206+
### L3 same-region failover
207+
208+
- [x] Add `RegionFilter` to `RouteQuery`; treat empty item region as `global`
209+
- [x] Thread region through `executor/chat.go` `pick` / `attemptsFor` / stream + non-stream loops, not only `pool.go`
210+
- [x] After the first pick, lock that account's region for the rest of the request
211+
- [x] Sticky-escape from a cooling pin stays in the same region
212+
- [x] Tests: Global A 429 → Global B, never CN; pin-CN cooling must not land on Global
213+
- [x] Merge before L4; mixed-pool UI without L3 will failover across sites
214+
215+
### L4 console
216+
217+
- [x] Add `qoder-cn` i18n keys and `AddAccountModal` option
218+
- [x] `accountProviderLabel` distinguishes Qoder CN from Qoder Global
219+
- [x] PAT helper text for CN points at `https://qoder.com.cn/account/integrations`
220+
221+
### L5 image and config
222+
223+
- [x] Install both `@qoder-ai/qodercli@1.1.27` and `@qodercn-ai/qoderclicn@1.1.27` in the image
224+
- [x] Export `QODERCLI_JS` and `QODERCNCLI_JS`
225+
- [x] README / CHANGELOG bilingual notes
226+
227+
### L6 acceptance
228+
229+
- [ ] Empty CN account → browser or PAT login → chat `只回复OK`
230+
- [ ] Import CN `qoder-native-v1` → daemon becomes hot
231+
- [ ] Mixed pool: pin stays on the matching CLI; Global 429 does not hit CN
232+
- [ ] Existing Qoder stream / tools / reasoning / usage / quota tests stay green
233+
- [ ] WorkBuddy path unchanged
234+
171235
## Later
172236

173-
- WorkBuddy / CodeBuddy as a second account provider — design only, see `docs/PROVIDERS.md`
174237
- Cursor provider
175238
- Exact tokenizer matching if Qoder starts returning richer usage
176239
- In-process multi-account (still impossible for Qoder WASM)
177240
- Anthropic `/v1/messages`
178241
- sub2api-style session-hash sticky
179242
- Optional truncated prompt/completion capture behind an explicit switch
180243

181-
Do not start WorkBuddy, Cursor, or other CLI providers until H7 is accepted. The
182-
provider-extension design lives in `docs/PROVIDERS.md`; do not add extra plan files.
244+
WorkBuddy J0–J4 are implemented; remaining WorkBuddy work is live-account
245+
acceptance, not a new design doc. Qoder CN design lives in `docs/PROVIDERS.md`.
246+
Do not add extra plan files. Do not start Cursor until Phase L is accepted or
247+
explicitly deferred.

0 commit comments

Comments
 (0)