sub2claude 把一批装好 Claude Code 的机器(每机一个 Claude 订阅、OAuth 登录)变成一个 OpenAI/Anthropic 兼容的中继 API——不用任何 model API key,算力来自这些机器的订阅额度。
- worker 用本地
claude -p出算力,不碰 model API key。 - 中控 server 负责:用户体系 · 按用户粘性会话路由 · 扣费 · session 控制 · 限流 · 后台。
- 脱胎于
sub2org(浏览器会话池):worker 从「浏览器扩展驱动网页」换成「CLI 驱动 headless Claude Code」;server / console 对 provider 透明、大量复用,核心新增只有一个 CLI worker daemon。
多台用户机(各跑一个 s2c)扇入 → 中心(1 台)← 扇入多台 worker 机(各绑 1 个订阅,各自直连 Anthropic):
flowchart LR
subgraph U["① 用户机器(每人一台,各跑 s2c)"]
direction TB
A["用户A · s2c(keyA)<br/>项目 /projA<br/>交互客户端 + daemon"]
B["用户B · s2c(keyB)<br/>项目 /projB"]
C["用户C · s2c(keyC) …"]
end
subgraph S["② 中心(1 台,单实例)"]
direction TB
SV["中心 server (Rust)<br/>鉴权 · 限流 · 计费<br/>粘性 · 池调度 · 中继表"]
CO["console 后台"]
DB[("postgres")]
end
subgraph W["③ worker 池(每台 1 个 Claude 订阅)"]
direction TB
W1["wk1 · worker + claude -p · 订阅1"]
W2["wk2 · worker + claude -p · 订阅2"]
W3["wk3 · … · 订阅3"]
end
AN(["api.anthropic.com"])
A -->|"socket.io(daemon) + HTTP /v1/chat"| SV
B --> SV
C --> SV
SV <-->|"socket.io(worker)<br/>dispatch / chunk·done·mcp_call"| W1
SV <--> W2
SV <--> W3
W1 -->|"推理·用订阅"| AN
W2 --> AN
W3 --> AN
SV -.- CO
SV -.- DB
中心的中继表是多用户隔离的关键(全部按 key 维度):
| 表 | 键 → 值 | 作用 |
|---|---|---|
daemons |
user_id → daemonSid |
每个在线 s2c 的 daemon(谁在线、桥回哪台机器) |
sessions |
session_id → {worker, claude_session} |
粘性:同一 user 续接回同一 worker + --resume |
pending |
req_id → {workerSid, daemonSid, charge} |
一次在途请求:结果发哪个 worker、工具桥回哪个 daemon、扣谁的费 |
mcp_pending |
call_id → workerSid |
一次工具调用:daemon 执行完把结果发回哪个 worker |
sequenceDiagram
autonumber
participant A as s2c-A · /projA
participant B as s2c-B · /projB
participant S as 中心 server
participant W1 as wk1 · 订阅1
participant W2 as wk2 · 订阅2
participant AN as Anthropic
A->>S: socket.io daemon(keyA) — 注册 daemons[A]
B->>S: socket.io daemon(keyB) — 注册 daemons[B]
A->>S: POST /v1/chat (keyA, 仅 prompt)
Note over S: authA→userA · 选空闲 wk1<br/>pending[rA]={wk1, daemonA, 费A}
S->>W1: dispatch rA (bridged)
B->>S: POST /v1/chat (keyB) — 与 A 并发
Note over S: authB→userB · 选空闲 wk2<br/>pending[rB]={wk2, daemonB, 费B}
S->>W2: dispatch rB (bridged)
W1->>AN: 推理(订阅1)
W2->>AN: 推理(订阅2)
W1->>S: mcp_call(rA, read src/x)
S->>A: 按 pending[rA].daemonSid=A 路由(只回 A 的电脑)
A-->>S: mcp_result(在 /projA 执行 read)
S-->>W1: mcp_result → claude(A) 拿到内容
W2->>S: mcp_call(rB, read src/y)
S->>B: 按 pending[rB].daemonSid=B 路由(只回 B 的电脑)
B-->>S: mcp_result(在 /projB 执行 read)
S-->>W2: mcp_result → claude(B) 拿到内容
Note over A,W2: ↻ A、B 各自循环工具(grep/edit/bash);edit·bash 前各自 s2c 弹 [y/N]
W1-->>S: chunk / done rA
S-->>A: 流式 SSE 答案A(按 userA token 结算·落 sessionA)
W2-->>S: chunk / done rB
S-->>B: 流式 SSE 答案B(按 userB token 结算·落 sessionB)
关键机制:
- 隔离:A 的
mcp_call按pending[rA].daemonSid只桥回 A 的电脑(/projA),B 的只回/projB——绝不串项目。 - 并发:每台 worker
capacity=1(一个订阅一次一个请求),A→wk1、B→wk2 互不阻塞;加机器即扩并发,单台被限流只冷却那台。 - 计费 / 粘性:各扣各的余额;同一 user 带
X-Sub2claude-Session续接 → 回同一 worker、--resume接上下文。 - 项目不上传:只发 prompt;claude 用工具按需拉它真正碰到的文件(时序图中间的 ↻ 循环)。
- 订阅只在 worker 消费:由真
claude -p——唯一合规路径,也是本地用s2c(我们的客户端)而非真·本地 Claude Code 的原因。
| 子项目 | 角色 | 技术栈 |
|---|---|---|
server/ |
中继控制中心:用户 / 鉴权 / 计费 / 粘性会话 / worker 池调度 / 后台 API | Rust(axum + socketioxide + sqlx/Postgres) |
worker/ |
worker daemon:驱动 headless Claude Code(claude -p),本仓核心新增 |
Node.js + socket.io-client |
bridge/ |
s2c 本地客户端:一条命令在本地项目起交互式编码会话,服务端 agent 的工具落到你本地项目执行——「脑在服务端、手在本地」。见 bridge/README.md |
Node.js |
console/ |
Web 控制台:admin(workers / sessions / users / billing / logs / metrics)+ 用户自助 | React 19 + Vite + Tailwind v4 |
deploy/ |
单实例部署:postgres + server + nginx | docker-compose |
server/console/deploy/由 sub2org 同名部分改名而来(sub2org→sub2claude、sk-sub2org-→sk-sub2claude-、X-Sub2org-*→X-Sub2claude-*、SUB2ORG_*→SUB2CLAUDE_*等),逻辑基本一致;worker/是全新的。
bridge/README.md——s2c本地客户端:安装 / 三种模式(交互 · headless · 一次性)/ 权限与安全。worker/README.md—— worker 部署、鉴权(claude setup-token)、安全(完整 Agent 沙箱)。worker/claude-bridge/README.md—— 与 Claude Code 交互的模块细节与事件映射。docs/USAGE.md/docs/DEPLOY.md—— 运营 / 消费 / 部署指南(worker 细节以worker/README.md为准)。CLAUDE.md—— 给 Claude Code 的仓库指南。