Skip to content

Commit 39839a0

Browse files
committed
docs(lingxi): README / TECHNICAL / CITATION 补灵犀渠道说明
- README(.en):渠道导语补灵犀;新增「灵犀」小节(wps_sid Cookie 导入、 一次对话三条请求、按 run 计费与缓存、私有 SSE 口径、schema 压平、 智点额度与签到、错误分类、ToS 灰区、节流);cache_control 覆盖面 6/10 - TECHNICAL:provider 表补 lingxi 行;新增 §5.8(三条请求、伪装桌面端、 kdocs 回落白名单、SSE / usage / schema 三处照抄口径、错误分类、 额度与签到);展示顺序权重补 lingxi - CITATION:摘要与 keywords 补 WPS 灵犀
1 parent b907d92 commit 39839a0

4 files changed

Lines changed: 60 additions & 6 deletions

File tree

‎CITATION.cff‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ message: "引用本服务时请使用下列信息(版本号以 /healthz 的 ve
33
title: "Coding2API"
44
abstract: >-
55
把 CodeBuddy(国内站 + 国际)、TRAE SOLO、OpenCode Zen、Kilo、Qoder、
6-
CodeArts、OfficeAce、小浣熊等上游渠道封装为 OpenAI / Responses / Anthropic
6+
CodeArts、OfficeAce、小浣熊、WPS 灵犀等上游渠道封装为 OpenAI / Responses / Anthropic
77
兼容 API 的单进程网关:公共凭证池、统一模型名与调度、按人用量统计。
88
上游均为逆向接口,仅供学习研究。
99
type: software
@@ -23,6 +23,8 @@ keywords:
2323
- qoder
2424
- codearts
2525
- raccoon
26+
- lingxi
27+
- wps
2628
authors:
2729
- family-names: Luo
2830
given-names: Hongsheng

‎README.en.md‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ Optional `temperature` / `max_tokens` / `reasoning_effort`; **leaving one empty
181181

182182
## Channels
183183

184-
> CodeBuddy (incl. international) / TRAE / Qoder / CodeArts / OfficeAce / Raccoon need credentials; **Zen and Kilo are credential-less free tiers** — one virtual placeholder row (scheduling / cooldown / stats as usual), quota reads "free tier (no quota API)", health stays "no probe". Virtual row re-seeds on restart after deletion; *pause* disables permanently.
184+
> CodeBuddy (incl. international) / TRAE / Qoder / CodeArts / OfficeAce / Raccoon / Lingxi need credentials; **Zen and Kilo are credential-less free tiers** — one virtual placeholder row (scheduling / cooldown / stats as usual), quota reads "free tier (no quota API)", health stays "no probe". Virtual row re-seeds on restart after deletion; *pause* disables permanently.
185185
186186
### OpenCode Zen (`zen`)
187187

@@ -272,6 +272,26 @@ The upstream is plain OpenAI protocol, with three quirks:
272272

273273
**Concurrency cap ~5** (the 6th concurrent request returns `429 user_model_rate_limited`) → `RACCOON_MAX_CONCURRENCY` defaults to 2, with its own pacer.
274274

275+
### Lingxi (`lingxi`)
276+
277+
[WPS Lingxi](https://lingxi.wps.cn), real WPS account required. **No login button**: the credential is the WPS account cookie `wps_sid` — log in on the web app (`lingxi.kdocs.cn` works as well), copy the whole Cookie from DevTools → Application → Cookies, and paste it into the *Login channels* panel (whole cookie string or a bare sid both work). Upstream has **no refresh endpoint**, and expiry is never guessed from age — the only real signal is a 401, after which you paste a fresh cookie.
278+
279+
**One conversation = three requests**: create a cowork session → claim a credits run → `agent-runtime/chat/completions` (private SSE). Upstream **bills per run** (several calls inside one run charge one credit), so sessions + runs are cached per account × model for 1 hour (no sliding renewal) with in-flight single-flight; `SQLNoRows` / "session not found" drops the cache and rebuilds once.
280+
281+
**Private SSE and usage semantics**: the text lives in `choices[].text` (not `delta.content`) and **no `[DONE]` is sent** (the stream ends on `finish_reason`); `usage.total_tokens` is the **current context size**, so it is recomputed as `prompt+completion`; `cached_tokens` / `reasoning_tokens` are flat fields with the opposite direction from OpenAI.
282+
283+
**Tool schemas must be flattened first**: `anyOf` / `oneOf` / `$ref` / array-valued `type` make the whole round fail with a single "AI service error"; `max_tokens` goes into `base_llm_arguments`, while `reasoning_effort` is sent at top level *and* inside `llm_arguments`.
284+
285+
**Quota = credits (智点)**: `total_balance` maps straight to remaining, bonus batches carry `bonus_credits[].expire_time` into the expiry ladder (15-day window); the ledger records credits, no token conversion. The catalog `multiplier` becomes the rate directly.
286+
287+
**Check-in**: the task center exposes `daily_check_in`; `status=claimed` means already checked in. Upstream answers duplicate claims with a fake 200 (`result=ok` while the balance does not move), so the reward is the **balance delta before/after**, and a zero delta falls back to the task status — HTTP 200 is never trusted.
288+
289+
**Error classification**: 401 → dead credential (re-paste cookie); 402 / quota wording → credit exhausted; 403 → transient; 404 → invalid request; 429 → named model only; audit rejection (`AuditFailed`) is not fixed by switching accounts; run expiry rebuilds once.
290+
291+
**ToS grey area**: reuses the desktop / web login state and impersonates the desktop client (`X-Client-Type: lingxi_claw` plus `x-kso-app-name/app-version/device-id`), see `provider/tos.py`.
292+
293+
**Pacing**: `LINGXI_CHAT_MIN_INTERVAL` defaults to 0 with its own pacer, hot-reloadable.
294+
275295
### CodeBuddy international (`codebuddy_intl`)
276296

277297
[www.codebuddy.ai](https://www.codebuddy.ai), same protocol as CN site but separate account system (separate registration and billing) → separate channel. Same device-code login.
@@ -332,7 +352,7 @@ claude
332352
- **`count_tokens`**: estimated locally, never forwarded.
333353
- **Images**: `image` blocks normalize to chat `image_url`; images inside `tool_result.content` are preserved.
334354
- **Unsupported**: `document` blocks, `source.type=file`, Anthropic server tools — explicit 400.
335-
- **`cache_control` passthrough** (on by default): landing spots match Anthropic (`system`, message content blocks, `tool_result` top level, `tool_calls` top level, `tools` top level). Structure validated only; values not interpreted. 6 of 9 channels accept it (codebuddy / codebuddy_intl / trae / qoder / codearts / kilo); zen and Raccoon unverified, OfficeAce not separately probed. Script `scripts/probe_cache_control.py`.
355+
- **`cache_control` passthrough** (on by default): landing spots match Anthropic (`system`, message content blocks, `tool_result` top level, `tool_calls` top level, `tools` top level). Structure validated only; values not interpreted. 6 of 10 channels accept it (codebuddy / codebuddy_intl / trae / qoder / codearts / kilo); zen, Raccoon and Lingxi unverified, OfficeAce not separately probed. Script `scripts/probe_cache_control.py`.
336356

337357
### CC Switch one-click import
338358

‎README.md‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ curl http://127.0.0.1:8000/v1/chat/completions \
183183

184184
## 渠道
185185

186-
> CodeBuddy(含国际)/ TRAE / Qoder / CodeArts / OfficeAce / 小浣熊 需凭证;**Zen 与 Kilo 无凭证免费层**——池里一条虚拟占位行(调度 / 冷却 / 统计照常),额度列「免费层(无额度接口)」,健康度恒「无探测」。删虚拟行重启复活;永久停用 →「暂停」。
186+
> CodeBuddy(含国际)/ TRAE / Qoder / CodeArts / OfficeAce / 小浣熊 / 灵犀 需凭证;**Zen 与 Kilo 无凭证免费层**——池里一条虚拟占位行(调度 / 冷却 / 统计照常),额度列「免费层(无额度接口)」,健康度恒「无探测」。删虚拟行重启复活;永久停用 →「暂停」。
187187
188188
### OpenCode Zen(`zen`)
189189

@@ -276,6 +276,26 @@ curl http://127.0.0.1:8000/v1/chat/completions \
276276

277277
**并发上限约 5**(第 6 个回 `429 user_model_rate_limited`)→ `RACCOON_MAX_CONCURRENCY` 默认 2,独立节流器。
278278

279+
### 灵犀(`lingxi`)
280+
281+
[WPS 灵犀](https://lingxi.wps.cn),需真实 WPS 账号。**没有登录按钮**:凭据是 WPS 账号 cookie `wps_sid`,在网页版(`lingxi.kdocs.cn` 同样可用)登录后 DevTools → Application → Cookies 复制整段,粘进「登录渠道」页(整段 Cookie 或裸 sid 均可)。上游**没有 refresh 接口**,且不按时长猜过期——真正的判据是上游 401,过期重贴即可。
282+
283+
**一次对话三条请求**:建 cowork 会话 → 领 credits run → `agent-runtime/chat/completions`(私有 SSE)。上游**按 run 计费**:一个 run 内多次调用只扣一次智点 → 会话 + run 按「账号 × 模型」缓存 1 小时(不滑动续期)+ 并发单飞;上游回 `SQLNoRows` /「会话不存在」时丢弃缓存重建一次。
284+
285+
**私有 SSE 与用量口径**:正文在 `choices[].text`(不是 `delta.content`),**不发 `[DONE]`**(靠 `finish_reason` 收尾);`usage.total_tokens` 是**当前上下文大小** → 自算 `prompt+completion`;`cached_tokens` / `reasoning_tokens` 是扁平字段,方向与 OpenAI 相反。
286+
287+
**工具 schema 必须先压平**:`anyOf` / `oneOf` / `$ref` / 数组型 `type` 会让上游整轮回「AI服务异常」,只回一句错误;`max_tokens` 要塞进 `base_llm_arguments`,`reasoning_effort` 顶层与 `llm_arguments` 各一份。
288+
289+
**额度=智点**:`total_balance` 直接进余额,赠送批次 `bonus_credits[].expire_time` 进到期阶梯(15 天内窗口);账本记「积分」,不做 token 换算。模型目录 `multiplier` 直接当倍率。
290+
291+
**签到**:任务中心 `daily_check_in`,`status=claimed` 即今日已签。上游对重复领取回 200 假成功(`result=ok` 而余额不动),故奖励按**领前 / 领后余额差**算,差为 0 时读任务状态判「已签」,绝不看 HTTP 200。
292+
293+
**错误分类**:401 → 凭证失效(重贴 Cookie);402 / 额度文案 → 额度耗尽;403 → 瞬时;404 → 请求无效;429 → 点名模型;审核拦截(`AuditFailed`)换号无用;run 过期 → 重建一次。
294+
295+
**ToS 灰区**:复用桌面 / 网页登录态并伪装桌面客户端(`X-Client-Type: lingxi_claw` + `x-kso-app-name/app-version/device-id` 固定可复现值),见 `provider/tos.py`。
296+
297+
**节流**:`LINGXI_CHAT_MIN_INTERVAL` 默认 0,独立节流器,可热更。
298+
279299
### CodeBuddy 国际(`codebuddy_intl`)
280300

281301
[www.codebuddy.ai](https://www.codebuddy.ai),同国内站协议,但两套独立账号体系(各自注册、各自计费)→ 独立渠道。登录链路同国内版(设备码)。

‎TECHNICAL.md‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ PROPOSAL.md 定方向,本文定实现。只写**当前代码现状**。
3737
| `src/provider/codearts/` | `signer.py` `dpop.py` `client.py` `events.py` `credential.py` `auth.py` `oauth.py` | SDK-HMAC-SHA256(纯函数)/ES256 DPoP(低 S)/上游 + 私有 SSE 还原 + 积分额度/SSE → 增量 Event(双形状)/凭证解析/OAuth2 PKCE/回调粘贴换 token |
3838
| `src/provider/officeace/` | `signer.py` `client.py` `events.py` `credential.py` `oauth.py` | 双签名(V11 + SDK-HMAC,纯函数)/上游 + 积分·签到·费率/错误分类 + `max_tokens` 改写/两套凭据 + 一次性 refresh/云端授权轮询(PKCE + DPoP + 开通校验) |
3939
| `src/provider/raccoon/` | `client.py` `events.py` `credential.py` `activity.py` | 标准 OpenAI 上游 + 积分余额·流水·心跳/SSE → Event + 分类(`user_model_rate_limited` → CONCURRENCY)/会话 JWT 归一 + claims 提取 + 稳定设备 id/懒发放签到核对 + 四池明细 + 到期阶梯 |
40+
| `src/provider/lingxi/` | `client.py` `events.py` `credential.py` | 上游 + 私有 SSE(`choices[].text`、无 `[DONE]`)+ 智点额度·签到/SSE → Event + 错误分类/wps_sid Cookie 归一 + uid / sid 哈希身份 |
4041
| `src/engine/` | `scheduler.py` `executor.py` `limits.py` `inflight.py` `half_open.py` `affinity.py` `continuation.py` `compress.py` `sticky_trim.py` `tool_output.py` `model_resolver.py` `reasoning.py` `prefix_debug.py` `sse.py` | 选号 + 冷却状态机 + pin/执行 + 目录收窄 + 轮换 + fallback + 统计埋点/限额判定 + 快照缓存/在途租约/冷却到期半开探测/会话粘性/截断续写/上下文压缩/粘性裁剪/tool 输出压缩/模型名解析/档位映射解析/前缀缓存诊断/SSE 帧解析 |
4142
| `src/compat/` | `openai/{request,response,errors,dsml}.py` `responses/{request,response}.py` `anthropic/{request,response}.py` | Chat 校验·聚合·error shape·DSML 泄漏回读/Responses 出口/Anthropic Messages 出口 |
4243
| `src/tasks/` | `pacer.py` `quota_probe.py` `checkin.py` `growth.py` `activity.py` `refresh.py` `retention.py` `backup.py` `alerting.py` `upstream_status.py` `model_probe.py` `status.py` `runner.py` | 全局节流器/额度探测/签到(`CHECKIN_TIMES` 空则每 10 分钟)/成长(仅 CB)/活跃上报/预刷新/小时汇总 + 90 天清理/备份/告警(七类规则 + webhook)/上游状态聚合/模型探活/`TASK_SPECS` + 运行态/调度接入 lifespan |
@@ -500,6 +501,17 @@ class Provider(Protocol):
500501
- **活跃上报**:`POST /api/web/auth/v1/devices_current_heartbeat`;头族 `x-client-device-id`(必填,缺了 `100002 params_invalid_error`)/ `x-client-platform: web` / `x-client-version` / `x-raccoon-language`。device_id 用 `derive_device_id(uid)` 稳定派生。
501502
- **额度明细**:`balance` 的四个池 → `Quota.packages`(0 的池不出条目;`topup_frozen` 时标 `usable=False`);`daily_grant.expire_at` → `Quota.expiry_ladder`(已被 `daily_expire` 消费的不计)。
502503

504+
### 5.8 灵犀(`lingxi`)
505+
**WPS 账号 cookie + 一次对话三条请求**,差异集中在 `client.py`:
506+
507+
- **凭据**(`credential.py`):接受整段 Cookie / 裸 `wps_sid`;`uid` 从 Cookie 段取,重登合并首选 `uid`、兜底 `sid-<sha256 前 16 位>`(与 `login_identity` 同口径)。**没有 refresh 接口**,过期只能重贴;判定只看上游 401,不按时长猜(猜错会把还能用的号摘掉)。
508+
- **三条请求**:`POST /api/aioffice/v1/cowork/sessions`(建 cowork 会话)→ `POST /api/public/v1/credits/runs`(领 run)→ `POST /api/public/v1/agent-runtime/chat/completions`。上游**按 run 计费**(一个 run 内多次调用只扣一次智点)→ `ensure_run` 按「账号 × 模型」缓存 1h(**不滑动续期**)+ 并发单飞;`SQLNoRows` /「会话不存在」丢弃缓存重建一次。
509+
- **伪装桌面端**:`X-Client-Type: lingxi_claw` + `x-kso-app-name/app-version/device-id`(固定可复现值)。端点默认 `https://lingxi.wps.cn`;账号面按主域 → `lingxi.kdocs.cn` 顺序回落(kdocs 账号的集群),白名单必须两者都含,漏收会让回落直接 404。
510+
- **SSE**(`events.py`):正文在 `choices[].text`(非 `delta.content`)、**不发 `[DONE]`**(靠 `finish_reason` 收尾);`usage.total_tokens` 是**当前上下文大小** → 自算 `prompt+completion`;`cached_tokens` / `reasoning_tokens` 是扁平字段。上游每帧是单行 `data:{…}`,不能用「空行分帧」的通用解析器(会吞帧)。
511+
- **工具 schema 压平**(`client.sanitize_schema`):`anyOf` / `oneOf` / `$ref` / 数组型 `type` 会让整轮回「AI服务异常」;`max_tokens` 注入 `base_llm_arguments`,`reasoning_effort` 顶层与 `llm_arguments` 各一份。
512+
- **错误分类**:401 → `DEAD`;402 / 额度文案 → `CREDIT`;403 → `SOFT`;404/422 → `INVALID`;429 → `MODEL`(点名模型);审核拦截(`AuditFailed`)→ `INVALID`(换号无用);run 过期 → `SOFT` + 重建一次。
513+
- **额度 / 签到**:`total_balance` 即智点余额,赠送批次 `bonus_credits[].expire_time` → 到期阶梯(15 天内窗口);账本记 `credits`,目录 `multiplier` 直接当 `credit_rate`。签到走任务中心 `daily_check_in`(`status=claimed` 即已签),奖励按**领前/领后余额差**算,差 0 时读任务状态——上游对重复领取回 200 假成功(`result=ok` 而余额不动),绝不看 HTTP 200。
514+
503515
## 6. 调度器
504516
### 6.1 选号规格
505517
```python
@@ -659,7 +671,7 @@ PRAGMA foreign_keys = ON; -- 库里没有任何外键:users ↔ api_keys
659671
- **哨兵名不跨渠道合并**(`_SENTINEL_LABELS` = `auto` / `default`):语义只在本渠道成立 → 退回原 id 归一键、各自单列。**同渠道内归一键重复 → 退回原 id 归一键**,且不登记歧义别名。
660672
- **对外 id 一律归一键**:多渠道取最短展示名的归一键;单渠道取该条;兜底/哨兵取原代号归一键。原代号不露面,转发经 `services.model_aliases` 映射回(`executor.upstream_model_name` 消费);每渠道登记三键(对外 id、原代号、展示名及 slug 形式)。
661673
- **撞车消歧**(`_disambiguate`):按 `_sort_key` 序保留先到者,后者依次取 `_fallback_ids`(原代号归一键 → 加渠道后缀 → 数字后缀)。兜底/哨兵条目用 `_scoped_key`(`渠道\0归一原代号`)隔离,对外 id 仍是原代号归一键。
662-
- **展示顺序**:`_PROVIDER_RANK`(codebuddy 0 → codebuddy_intl 1 → trae 2 → qoder 3 → codearts 4 → officeace 5 → raccoon 6 → 其余 7),多渠道取 `min`,同级按归一键字典序。只影响展示;调度选号顺序(`KNOWN_PROVIDERS`)不变。
674+
- **展示顺序**:`_PROVIDER_RANK`(codebuddy 0 → codebuddy_intl 1 → trae 2 → qoder 3 → codearts 4 → officeace 5 → raccoon 6 → lingxi 7 → 其余 8),多渠道取 `min`,同级按归一键字典序。只影响展示;调度选号顺序(`KNOWN_PROVIDERS`)不变。
663675
- **自定义别名**(`MODEL_ALIASES`,热更,`model_alias.py` 解析 + `api/model_merge._apply_custom_aliases` 注入):`对外名=模型@渠道;对外名2=模型`,`@渠道` 可省。解析宽容(坏段忽略 + warning),注入期判撞名 / 目标不存在 / 渠道不持有 → 该段 ERROR 跳过;条目带 `custom_alias: true`。**无别名链**。用法见 README「模型名三字段」。
664676

665677
### 9.2 按凭证加载与 SWR
@@ -919,7 +931,7 @@ PRAGMA foreign_keys = ON; -- 库里没有任何外键:users ↔ api_keys
919931
`CREDENTIAL_PROXY_POOL`(启动期,分号分隔)非空时,同一凭证固定走同一出口:
920932

921933
- 分配 = `sha256(credential_id) → int % len(pool)`,**不依赖进程状态**,重启后不漂移(进程内轮转会等于没做)。池成员增删会让部分凭证换桶——改池子本身就是要换出口。
922-
- 凭证 id 走 contextvar(`request_context.set_credential_id`),由 engine / 任务层设置:推理(executor `_stream_source`,每次换号重试都重设)、额度探测、签到、token 预刷新、成长中心、管理台单条 probe/checkin。**不依赖凭证 data 里的账号字段**(九个渠道字段名不统一,缺失就会静默回落直连)。
934+
- 凭证 id 走 contextvar(`request_context.set_credential_id`),由 engine / 任务层设置:推理(executor `_stream_source`,每次换号重试都重设)、额度探测、签到、token 预刷新、成长中心、管理台单条 probe/checkin。**不依赖凭证 data 里的账号字段**(十个渠道字段名不统一,缺失就会静默回落直连)。
923935
- `httpx` 的代理是连接池级参数,所以 `LazyClients` 按 `(流/短, 代理)` 维护多份池(`_pools`);未配池时只有一条池,行为与之前逐字节一致。显式注入的 client(测试替身)永远优先。
924936
- 优先级:凭证池分配 → `PROVIDER_PROXIES[渠道]` → 直连。**OAuth 登录阶段不覆盖**:登录时凭证还没入库(没有 id),仍走渠道级代理/直连。
925937

0 commit comments

Comments
 (0)