把 CodeBuddy(国内 + 国际)、TRAE SOLO、OpenCode Zen、Kilo、Qoder、CodeArts、OfficeAce 八个上游封装成 OpenAI 兼容 API,提供公共凭证池、统一模型名与调度、按人用量统计。
Warning
逆向接口,仅供学习研究,未做安全审计;公网部署需反向代理 + 鉴权 + IP 白名单。
- OpenAI 兼容出口:
/v1/chat/completions(流式 / 非流式)、/v1/responses(Codex CLI)、/v1/messages(Claude Code)、/v1/models(含单模型查询)、/v1/user/balance。 - 统一调度:扁平模型名按健康度选号,
模型@渠道强制指定。免费渠道优先 → 在途最少 → 到期额度 → 健康度与分级冷却 → 余额多者优先。模型级限流只避让该模型;多轮对话粘同一凭证,出错才轮换。 - 到期额度优先:主窗口(默认 36h)内将过期的额度先用,打平再比 7 天窗口。额度单位统一为积分。
- 模型列表按凭证加载:只展示当前有可用凭证的渠道,未接入不出现幽灵模型。
- 公共凭证池:admin 集中维护、全员共享、加密入库(
APP_SECRET);设备码登录、多账号切换、额度探测、每日签到、token 预刷新。 - 三角色账号:
admin/operator/viewer,存 SQLite;一次性激活链接、首登强制改密、改角色或停用即时吊销会话;登录与写操作留审计。 - 成长中心(仅 CodeBuddy):自动领礼、派 Buddy、领任务奖、断登补登、连登兑换、开盲盒;不可逆动作可用
GROWTH_IRREVERSIBLE_ACTIONS=false关停。 - 脱敏统计:不存对话内容;明细 90 天、小时汇总永久;按人 / 渠道 / 模型 / 凭证可视化;成本按 OpenRouter 刊例价估算;输出速度分正文 / 总产出两口径;明细支持 SSE 实时流。
- 上游状态聚合:并排展示各提供方官方状态页,区分「上游故障」与「本服务凭证 / 额度问题」。只做解释、不参与调度;快照只存内存。
- 侧栏数据提醒:侧栏显示待办计数角标(需重登 / 耗尽、未恢复告警、任务失败、异常上游、待激活账号、将到期 Key、目录陈旧),按角色裁剪。见下文。
- 管理台安全加固:登录限流、CSRF 校验、请求体上限、Host 白名单。
- 窄屏表格转卡片:<768px 时一行 = 一张卡,列名收成可点的排序片;DOM 不变,读屏语义一致。
协议层实现(私有信封、签名、门禁伪装等)见 TECHNICAL.md。
前置:Python 3.12+、uv、Node.js 24+ 与 pnpm 10+(仅构建前端需要)。
uv sync
uv run python scripts/create_user.py admin --role admin # 建首个管理员
cd web && pnpm install && pnpm build && cd ..
APP_SECRET="换成你自己的随机字符串" \
uv run python -m uvicorn src.main:build_app --factory --port 8000打开 http://127.0.0.1:8000 登录管理台。之后加人都在「用户管理」页。
scripts/create_user.py 直接写 SQLite(--db 或 DATA_DIR,默认 data/coding2api.sqlite3)。老部署的 secrets/users.txt 启动时导入一次(不覆盖已存在的)。
Docker:
cat > .env <<'EOF'
APP_SECRET=换成你自己的随机字符串
PUBLIC_BASE_URL=http://127.0.0.1:8000
EOF
docker compose up -d --build
docker compose exec coding2api python scripts/create_user.py admin --role admin拉取镜像:docker compose pull(或 docker pull ghcr.io/robbsluo/coding2api:v0.9.1)。打 tag v* 推到 main 即触发 .github/workflows/publish.yml,同时发布 <tag> 与 :latest。
账号存在 SQLite 的 users 表,管理台「用户管理」页(仅管理员)负责增删改。
| 角色 | 能做什么 |
|---|---|
admin |
用户管理、任务与配置、凭证读写、全部统计、审计日志、服务端日志、上游状态、模型列表 |
operator |
凭证读写(导入 / 删除 / 启停 / 签到 / 成长 / 切换账号)、全部统计、上游状态、模型列表 |
viewer |
只读:仪表盘、凭证列表、统计、Playground |
- 加人:填用户名 + 角色,生成一次性激活链接(24 小时有效,只显示一次),对方打开
/activate自设密码。系统里不存在「管理员知道但用户不知道」的初始密码。重置密码同理。 - 改角色 / 停用:即时作废该账号所有会话 Cookie。停用是可逆的默认「删人」方式(用量统计仍归属该用户名);硬删只在 CLI。
- 自助改密:右上角用户菜单 →「修改密码」,需当前密码。
- 防锁死:不能停用 / 降级最后一个活跃管理员,也不能对自己降级或停用。
CLI 兜底(无 Web / 全部管理员失联):
python3 scripts/create_user.py alice --role admin # 交互输入密码
python3 scripts/create_user.py --list
python3 scripts/create_user.py alice --delete --force # 硬删,不可逆
python3 scripts/create_user.py --db data/x.sqlite3 ...
echo "$PASSWORD" | python3 scripts/create_user.py alice --role admin --password-stdinADMIN_USERNAMES 与 secrets/users.txt 只在引导期生效(首个管理员、老部署升级),此后角色只来自 DB。
「审计日志」页(仅管理员)记录登录与写操作,可按操作者 / 动作筛选、分页:登录(成败都记)、账号变动、凭证写操作、告警人工处置。每条含时间、操作者、动作、对象、说明与来源 IP。不含密码或令牌明文(激活令牌只存 SHA-256 摘要)。保留期同请求明细(默认 90 天)。
「服务端日志」页(/logs,仅管理员)是只读的进程内实时日志视图:GET /api/logs 取最近若干行,GET /api/logs/stream 走 SSE 先回放再推新行。支持级别筛选、关键字过滤、跟随最新、暂停、清空,无任何写入入口。
- 只收本进程
logging记录,不读文件、不 tail;多进程下各看各的。 - 脱敏是硬前提:每行发出前过白名单,命中
token/secret/authorization/cookie/password/api_key、sk-…形状或Bearer <值>的整行丢弃。 - 缓冲容量
LOG_BUFFER_LINES(默认 500,可热更),环形覆盖最旧;0关闭整页(两个端点 404)。 - 单行上限 4000 字符;每行带
seq、时间、级别、logger、request_id。订阅者上限 4,超限 429 +Retry-After。
- 「凭证管理」添加凭证:
- CodeBuddy(含国际):设备码登录,或粘贴
{"token":"..."}。 - TRAE:粘贴凭证 JSON(
accessToken/uid/refreshToken)或回调链接(apiHost只接受官方地址)。 - Qoder / CodeArts:设备码 / OAuth2 授权码登录(CodeArts 授权后把打不开的本地回调链接粘回页面)。
- OfficeAce:点「登录 OfficeAce」走华为云授权(回调由云端承接)。
- OpenCode Zen / Kilo:无需添加,启动时自动出现虚拟凭证(删了可在同面板补回)。
- CodeBuddy(含国际):设备码登录,或粘贴
- 「API Key」创建
sk-...(明文仅显示一次)。 - 调用:
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Authorization: Bearer sk-你的key" \
-H "Content-Type: application/json" \
-d '{"model":"glm-5.2","messages":[{"role":"user","content":"你好"}]}'模型@渠道 强制指定上游(如 glm-5.2@trae、mimo-v2.5-free@zen),不写则自动选。任意 OpenAI 兼容客户端可直接接入(Base URL http://127.0.0.1:8000/v1)。「Playground」页用登录会话直接测试,无需 API Key。
每个模型统一三个字段:
- 原代号(
raw_id):渠道真正发的 key,永不改动。各渠道内部代号不同(Qoderkmodel_latest= TRAEkimi-k3-1),可能带命名空间前缀(Kilo 的kilo-auto/free)。 - 归一键:剥掉免费档标记(
-free/free路径段)与命名空间前缀后 slug 化,如kilo-auto。模型列表按它判断「是不是同一个模型」并合并。 - 展示名(
name):清洗后的可读文本,如LongCat 2.5 Preview。上游给了可读名就用,否则从原代号派生;品牌写法按官方纠正(DeepSeek/MiMo/GLM/GPT)。
GET /v1/models 里所有 id 都是归一键,转发时自动换回原代号,by_provider.{渠道}.raw_id 可见。三种写法都能直接请求。同渠道内归一键重复时退回原代号。
价格与档位:条目可选带 cost {input, output, cache_read}(USD / 百万 token,与成本估算同一份价表)与 reasoning_efforts(该渠道生效的档位集合)。有才给,缺价不带该字段。
自定义别名(MODEL_ALIASES,可热更):格式 对外名=模型@渠道;对外名2=模型,@渠道 省略则自动选。列表与请求解析用同一份,条目带 custom_alias: true。撞名、目标不存在、渠道不持有该模型时该段记 ERROR 并跳过;不支持别名链。
单模型查询(GET /v1/models/{id}):三种写法 + 自定义别名都认,大小写不敏感,带命名空间的原代号也能查。找不到或不在 Key 白名单内一律 404(code=model_not_found),不泄露池里有哪些模型。
n 参数只接受 1:出口结构只能产生一个候选,n != 1 一律 400。
- 收尾时在 finish chunk 后、
data: [DONE]前补一帧choices: []的 usage;上游没上报就不补,未上报字段一律null。 - 思考 token 需客户端带
reasoning_effort;CodeBuddy 渠道未给时补medium(否则思考会写进正文),显式给的值原样透传。
八条渠道的上游流读取都是 read=None,网关自己兜:
- 首事件超时(
STREAM_FIRST_EVENT_TIMEOUT_SECONDS,默认 0):客户端还没收到内容时超时,可安全换号重试,记upstream_first_event_timeout。注释帧 / 心跳不重置计时。 - 流内空闲超时(
STREAM_IDLE_TIMEOUT_SECONDS,默认 0):已出帧后超时只能断流并以错误帧收尾,不换号(半截输出无法回滚)。
两项默认关闭是刻意的:实测慢上下文首字可达 40–71s。要用请在管理台按部署实际打开(推荐 60 / 300)。
可选填 temperature / max_tokens / reasoning_effort,留空即不发送该键。前端校验边界与后端一致(temperature ≤ 1000、max_tokens ≤ 4,000,000)。reasoning_effort 只在模型支持推理时出现,默认「不发送」。
CodeBuddy(含国际)/ TRAE / Qoder / CodeArts / OfficeAce 需填凭证;OpenCode Zen 与 Kilo 是无凭证免费层——池里是一条虚拟占位行(调度 / 冷却 / 统计照常),额度列显示「免费层(无额度接口)」,健康度恒为「无探测」。虚拟行删除后重启会复活,永久停用请用「暂停」。
接 opencode.ai/zen 的免费模型,无需登录。上游清单混着付费模型且不带免费标记,本服务按 -free 后缀收窄再逐个探活,只把匿名真正可用的放进 /v1/models(判活缓存 30 分钟,免费模型标 x0)。
免费层门禁:上游要求伪装官方客户端(UA 版本、会话头、stream:true、tools 含 bash/read),本服务自动满足;注入的 bash/read 是空壳,模型真调用时被过滤。UA 用 ZEN_OPENCODE_VERSION,端点用 ZEN_API_ENDPOINT(须在 ZEN_ALLOWED_ENDPOINTS 内)。付费模型强制 模型@zen 也只报不可用。
接 Kilo(api.kilo.ai/api/gateway)的免费模型,无需登录。标准 OpenAI 协议,上游 /models 每条带 isFree,据此直接过滤,不做探活(省免费配额)。
额度与限流:网关级约 200 请求/小时/IP。上游 429 与 502/503/504 都是模型级——只冷却被点名的模型。端点用 KILO_API_ENDPOINT(须在白名单内)。
接 Qoder,需真实账号:点「登录 Qoder」完成设备码授权(Token 不经过浏览器)。上游是私有 COSY 协议,本服务负责签名与解包,对外仍是标准 OpenAI。
额度与签到:支持额度探测(/api/v2/quota/usage)与每日签到(积分可累积)。签到为活动制(/sash/api/v1/me/campaigns),旧 daily-check-in 仅作回退。签到活动每日 10:00(UTC+8)刷新(领取后 30 天有效),后台按该窗口封账。新协议不提供连续天数。国际站没有签到接口(404),记为「本区域无此接口」。
端点与节流:默认国内版(openapi.qoder.com.cn + gateway.qoder.com.cn),国际站改 QODER_API_ENDPOINT=https://openapi.qoder.sh 并把域名加进白名单。QODER_CHAT_MIN_INTERVAL 默认 5,可热更。
上游节点故障:Qoder 有时把自身节点故障包成 400([FAIL]node:…),本服务识别为模型级瞬时故障,只冷却该模型,耗尽候选时回 503(no_healthy_credential)。
接 华为云 CodeArts,需真实账号:点「登录 CodeArts」,授权后浏览器跳到打不开的本地地址,把整条链接粘回页面换取 STS 凭证(AK/SK + security_token + refresh_token + DPoP 私钥)并加密入库。
令牌刷新是刚性的:refresh_token 与 client_id=codearts-agent、DPoP 私钥三者绑定且一次性,刷新后必须回写新 refresh_token。由预刷新任务自动完成(DPoP ES256/P-256)。预刷新窗口封顶 45 分钟且必须宽于轮询周期。凭据被作废时标记「需重新登录」并停止自动重试。
每日签到:GET /v1/ops/delivery → POST /v1/ops/claim → POST /v1/ops/confirm,后台每日自动领取。CLAIMED 是「已领未确认」,两步一起做。
额度=积分余额:探测走 GET {snap}/snap-manager/v1/statistics/plugin(前缀必须带 snap-manager,头 Agent-Type: PromptCenter)。上游数值即积分,不做折算;分项包走悬浮明细,并登记套餐到期日(积分过期同样作废)。
签名 / 累计全文 SSE / 节流:华为云 SDK-HMAC-SHA256(AK/SK + X-Security-Token),白名单须含 snap 引擎、STS 与门户。流式 text 是累计全文(替换语义),本服务还原为增量。上游硬限每账号并发 3,故有 CODEARTS_MAX_CONCURRENCY 与 CODEARTS_REQUEST_WINDOW_SECONDS。
接 华为云 OfficeAce,需真实账号:点「登录 OfficeAce」完成华为云授权即可,回调由云端承接,不监听本地端口。后端按 2.5s 轮询 /v1/claw/auth/code 取回授权码。
开通前提:账号必须先在 OfficeAce 客户端登录并兑换邀请码。未开通的账号登录「成功」但拿不到模型密钥,本服务会点名报出「华为云账号 xxx 还没开通 OfficeAce 服务」。
两套凭据,寿命不同:
| 凭据 | 寿命 | 用途 |
|---|---|---|
Basic model_app_key:model_app_secret |
不过期 | 聊天转发 |
临时 AK/SK + security_token + project_id |
约 2 小时 | 积分查询、签到 |
混用会得到 APIG.0303 app not found。临时凭据由预刷新任务续期,refresh_token 一次性轮换且与 client_id=pdp5_for_agentarts + DPoP 私钥绑定(与 CodeArts 票不通用)。
上游就是标准 OpenAI 协议,但有三处特例:
- 限流走伪 200:HTTP 200 +
data:{"error":…},错误帧照常交引擎分类,81111/81114只冷却「该凭据 × 该模型」。 - 模型级拒绝不牵连整条凭据:
81004/81009走 403/404,业务码判定先于状态码,归为模型级负缓存(成功即撤销)。 max_tokens是推理 + 正文总预算:≥16时自动追加 1024 余量再夹到该模型硬上限,<16原样透传,未给不补。
积分即主账:单位是 officeace_points,remaining/total 直接是积分。每笔奖励独立到期、不合并重置,故额度口径写作「逐笔到期」。
每日签到:POST /v1/subscription/bonus/claim(写操作,调用即真发积分;body 必须 `` + JSON Content-Type)。上游对当日重复领取幂等。收获按剩余量增量计算。封账到下个北京时间 0 点。
不做主动探活 / 不做 credit 估算:探活要真发推理消耗积分;cost_coefficient 与 tokens_per_point 的叠乘关系官方未说明。模型级拒绝改由真实请求按 BLOCKED 负缓存处理。
签名与端点:控制面 /v1/claw/* 用 SDK-HMAC-SHA256;模型网关只认 V11-HMAC-SHA256,用错回 APIG.0624 或 401。OFFICEACE_ALLOWED_ENDPOINTS 只需控制面主机(模型网关每账号不同,由上游签发存在凭证里)。OFFICEACE_CHAT_MIN_INTERVAL 默认 0。
单账号单用户:官方《套餐管理》限制单账号单用户,不做多人共用。
换账号:登录按钮旁有「换账号」(force_login=true),先清掉浏览器里的华为云会话再授权。
接 www.codebuddy.ai,与国内站同协议,但是两套独立账号体系(各自注册、各自计费),故为独立渠道。登录链路与国内版一致(设备码)。
不合并成一个渠道的原因:CODEBUDDY_API_ENDPOINT 是进程级单值,切过去会让存量国内站凭证打到国际站。两站可同时持有凭证,各走各的端点、节流器与登录流程。
白名单刻意互不含:CODEBUDDY_INTL_ALLOWED_ENDPOINTS 默认只有国际站主机,填国内站地址会在启动时报错(跨站发送即凭证泄露)。
能力差异:聊天、模型列表、额度探测、token 刷新、活跃上报全部启用;每日签到、成长中心未启用(签到渠道侧就没有该活动,成长中心无法验证且含不可逆动作)。
模型列表按套餐探活:用当前凭证逐个发一次最小请求,只保留真正可用的模型;11158(套餐不允许)与 11102(无此模型)剔除,429 / 5xx / 超时保留。结论按账号缓存 6 小时。
同名模型不带渠道后缀:两站共有的模型在 /v1/models 里是一条、挂两个渠道;-x / -sg 这类真实不同的模型各占一条。
节流:CODEBUDDY_INTL_CHAT_MIN_INTERVAL 默认 5s,独立节流器,可热更。
PROVIDER_PROXIES="codebuddy=http://127.0.0.1:7890;qoder=socks5://127.0.0.1:1080"
格式 渠道=代理URL;渠道2=代理URL2,协议支持 http/https/socks5/socks5h,留空 = 全部直连。作用于该渠道全部出站请求(聊天、额度、模型拉取、后台任务、OAuth)。启动期项,改后需重启。未知渠道 / 非法协议 / 缺 = 一律启动失败。环境变量 HTTP_PROXY 等一律不采信(trust_env=False)。
容器内
127.0.0.1指容器自身。代理跑在宿主时写host.docker.internal:PROVIDER_PROXIES="codebuddy=http://host.docker.internal:7897"compose 已声明extra_hosts。解析只校验格式不校验可达,写错不会启动失败。
POST /v1/responses 提供 Responses 子集,与 chat 共用同一套选号 / 冷却 / 轮换 / 统计与会话粘性:
export CODING2API_KEY=sk-你的key
codex -c "model_providers.coding2api={ name='coding2api', base_url='http://127.0.0.1:8000/v1', wire_api='responses', env_key='CODING2API_KEY' }" \
-c model_provider=coding2api -c model='glm-5.2' '你的任务'支持文本、流式正文、思考摘要、函数工具调用、finish_reason=length → response.incomplete。text.format 映射到 chat 的 response_format。内容 part 映射:input_text / text / output_text → text,input_image → image_url。不支持 store=true、previous_response_id、input_file、Responses 私有工具——一律显式 400。include=["reasoning.encrypted_content"] 接受但忽略。
只放行 OpenAI 定义的三种形状,畸形或未知显式 400:{"type":"text"}、{"type":"json_object"}、{"type":"json_schema","json_schema":{name, schema, description?, strict?}}(内部未知字段丢弃)。Responses 出口的 text.format 与 chat 入站共用同一段校验。
export ANTHROPIC_BASE_URL=http://127.0.0.1:8000
export ANTHROPIC_AUTH_TOKEN=sk-你的key # 或 ANTHROPIC_API_KEY(走 x-api-key 头)
claude- 鉴权:
x-api-key与Authorization: Bearer都接受。 - 流式:
message_start→content_block_*→message_delta→message_stop;无[DONE],thinking 块在content_block_stop前补signature_delta。 - 缓存命中:出口 usage 拆两笔——
input_tokens只计未命中,命中另记cache_read_input_tokens。 count_tokens:本地估算,不转发上游。- 图片:
image块归一为 chatimage_url;tool_result.content内的图片同样保留。 - 不支持:
document块、source.type=file、Anthropic 服务端工具,一律显式 400。 cache_control透传(默认开启):落点与 Anthropic 一致(system、消息内容块、tool_result顶层、tool_calls顶层、tools顶层)。只做结构校验,取值不解释。八渠道实测 6 个接受(codebuddy / codebuddy_intl / trae / qoder / codearts / kilo);zen 未验证、OfficeAce 未单独验证。脚本scripts/probe_cache_control.py。
「API Key 管理」页可生成 ccswitch:// 深链(Claude Code / Codex CLI / OpenCode 各一个按钮)。入口两处:创建 Key 成功后的对话框、Key 列表每行的「导入 CC Switch」。深链含 Key 明文,只在用户主动打开时生成。需 CC Switch v4.0.10+。注意 endpoint 形态随 apiFormat 变:Anthropic 填到根(不带 /v1),OpenAI 填到 /v1。本服务无 Gemini 出口。
GET /v1/user/balance 兼容 DeepSeek 形状,Bearer sk-... 鉴权,供 Cherry Studio / cc-switch 显示余额。数据来自额度探测缓存,按可用凭证汇总:
{
"is_available": true,
"balance_infos": [{"currency": "credits", "total_balance": "60.00",
"granted_balance": "0.00", "topped_up_balance": "0.00"}],
"balance_known": true,
"providers": [{"provider": "codebuddy", "remaining": 50.0, "total": 150.0, "credentials": 2}]
}从未探测成功时 balance_known 为 false(未知,不是 0)。
GET /health:只回{"status":"ok"},适合存活探针。GET /healthz:额外给凭证池计数,ready == 0时回 503(就绪探针)。
两个端点都不鉴权,且都接受 HEAD。五类计数互斥且合计 = total,与调度器同口径。把存活探针配在 /health,/healthz 用作就绪 / 告警。
升级注意:
/healthz的状态从「恒 200」变为「ready=0 时 503」。
默认开启、不鉴权;METRICS_ENABLED=false(可热更)时 404。
| 指标 | 类型 | 标签 | 数据源 |
|---|---|---|---|
coding2api_requests_total |
counter | provider / model / status |
usage_hourly |
coding2api_tokens_total |
counter | provider / type |
usage_hourly |
coding2api_credentials |
gauge | state |
凭证池 |
coding2api_upstream_errors_total |
counter | provider / kind |
进程内计数(重启归零) |
coding2api_task_failures_total |
counter | task |
进程内任务运行态(重启归零) |
不含 credential_id / api_key_id / username(高基数)。requests_total / tokens_total 读永久保留的小时汇总,跨重启单调。小时汇总没有 error_type 维度,故 kind 由进程内计数器提供。
- 渠道绑定:该 Key 只在该渠道凭证里选号;模型属另一渠道时 400 并指出实际归属。留空 = 跨渠道自动。
- 来源 IP 白名单:逗号分隔 IP 或 CIDR,写入时规范化(
10.0.0.1→10.0.0.1/32),不在白名单返回 403。默认不采信X-Forwarded-For;仅TRUST_PROXY=true时按 XFF 取最后一个条目。 - 模型白名单:逗号分隔模型名或 fnmatch glob(如
glm-*,kimi-k3),不区分大小写,模型@渠道后缀不参与匹配。白名单外 400,/v1/models也只列白名单内模型。 - 到期时间:epoch 秒,留空 = 永不过期,到期后 401(与「Key 不存在」统一文案)。
- 配额:六项独立,
0或留空 = 不限制——daily_requests/daily_tokens/daily_cost_usd/rpm/tpm(用预估值判定)/max_concurrency。当日类按北京时间自然日,速率与并发是进程内滑动窗口。超限 429 +quota_exceeded,Retry-After指向可重试时刻。判定出异常时一律放行并记 warning。 - 自助查询:
GET /v1/usage?days=1|7|30返回自己的用量、剩余额度与当前速率占用(只读本地明细)。
「暂停」把该凭证摘出对话流量,并让每日签到 / token 预刷新跳过它;其余后台任务(额度探测、成长中心、活跃上报)照常运行。适合「先不接聊天、但积分继续领」。「取消暂停」立即放回池子。与「已禁用」不同——后者是渠道判定会话失效后的系统禁用,需重新登录后「恢复」。同一开关也在 POST /api/credentials/{id}/toggle。
Zen / Kilo 是虚拟占位行,暂停是永久停用的唯一方式(删除后重启会补回)。暂停也会把该渠道从模型列表摘掉。
凭证页支持多选 + 批量操作条(探测额度 / 每日签到 / 暂停 / 取消暂停 / 恢复)。
- 端点:
POST /api/credentials/batch,body{action: "probe"|"toggle"|"checkin"|"revive", ids: [...], enabled?}。一次最多 200 条,重复 id 去重。 - 逐条隔离:单条失败不影响其余。响应
{action, total, succeeded, failed, results: [{id, ok, error?, detail?}]}。 - 暂停是显式状态而非取反:
toggle的enabled由请求体显式给出,故操作条拆成「暂停」与「取消暂停」两个按钮。 - 「恢复」与「取消暂停」是两件事:前者
revive(解系统硬禁用,需先重新登录),后者toggle enabled=true。 - 只收已有单条端点的动作:
refresh与delete不走这个通道。 - 审计只记一条汇总(
toggle/revive);审计写失败只记 warning,不影响操作结果。
凭证级可选护栏,行内菜单「设置限额」。四项独立,全部留空 = 不受限。判定在选号前完成;后台任务照常运行。
- 保留积分:剩余积分 ≤ 该值时整号停用。余额未知时不猜。
- 每日 token 上限 / 每日积分上限:当日累计达到即整号停用。
- 按模型 token 上限:
{模型: 上限},只停该模型。 - 当日 = 北京时间自然日,0 点刷新。
- 全部候选因限额被跳过时回
no_healthy_credential503;模型级冷却回model_unavailable。 - 写入
PUT /api/credentials/{id}/limits(整体替换,四个全关时删除配置行)。即时生效。审计动作credential.limits。
MAX_INFLIGHT_PER_CREDENTIAL(默认 0 = 不限):同一凭证并发达到该值后不再参与选号。
- 计数是进程内的(单实例语义)。
- 租约在请求结束时释放,另有 600s TTL 兜底。
- 全部占满时与限额护栏同口径回 503。
- 可热更。
- 在途计数兼作负载键:插在「免费优先」之后、到期额度之前,并发自动摊到空闲号。
- 筛选工具条:搜索(昵称 / 渠道 / id)× 渠道 × 状态 × 标签,四者 AND,纯前端收敛。显示「筛选后 N / 总数」并提供「清空」。标签下拉只在池里真出现过标签时渲染。标签不参与搜索词匹配。
- 标签:逗号分隔,最多 16 个、单个 ≤ 24 字符。只用于筛选与展示,不参与选号。行内最多列两个,多余收成
+N。 - 组排序:只重排渠道组,不动组内行序。默认额度从高到低,可切回固定渠道顺序。无额度接口的免费层垫底。
- 按渠道分组:组头是紧凑仪表盘——图标 + 渠道名 + 账号数、模型(数量 + 最低倍率,只算收费模型;整组免费才写「全免」;自定义别名不算独立模型)、状态分布、额度合计、到期积分(主窗口 1.5 天一块,次窗口 7 天仅在更多时再出一块)。默认全折叠,筛选生效时自动展开。点组头折叠 / 展开。
- 列头跟着展开区走:列头渲染在每个展开组内部并吸顶,各组共用同一套排序状态。「成长中心」列只在 CodeBuddy 出现,其余渠道留空占位以保持列宽。
- 长列表可滚:表格限高 70vh,吸顶列头与「操作」按钮常驻。
额度列数字带虚线下划线,悬浮显示本行的周期口径。
GET /v1/models 与 Playground 下拉只合并当前有可用凭证的渠道(至少一条未暂停、未会话失效的记录)。冷却中的凭证仍算有凭证。展示顺序:CodeBuddy、TRAE、Qoder、CodeArts、OfficeAce 在前,组内按名字典序,多渠道模型按最高优先级渠道归位。
请求路由只认这份模型缓存列表:目录能证明归属时,候选只有那些渠道;全部不可用时直接 503,不再逐渠道试。模型完全不在目录里时仍按全部候选路由;直连指定与 Key 渠道绑定不受影响。
模型表除进程内 TTL 缓存(300s)外,每次拉取成功还写快照到 DATA_DIR/model_catalog.json 并在启动时同步读回,重启那一刻归属表就已就绪。快照损坏或超 7 天只丢缓存。另有 MODEL_CATALOG_MINUTES 后台任务兜底。
Playground 与 /v1/models 走 stale-while-revalidate:有缓存立刻回旧列表,TTL 到期后台刷新;某渠道一条缓存都没有时才同步等。连续失败时重试间隔按 2^(n-1)×TTL 退避(封顶 2 小时),日志只在首次失败打 WARNING。
(GET /api/model-catalog,管理员 / 操作员)只读展示 OpenRouter 公开目录:刊例价、上下文 / 输出上限、模态、能力、知识截止、Artificial Analysis 三项指数(非本服务实测,不做排序)。每行带 available 布尔,页面提供默认关闭的「只看当前渠道」开关。
疑似下线视图:GET /api/models/availability(operator)把最近 N 天(默认 7,可调 1–30)upstream_blocked / invalid_request 的失败按 (渠道, 模型) 聚合。行内「加入黑名单」把该模型追加到 model_blocklist,走既有设置接口并落审计。
浮动别名取同族最新快照的分:OpenRouter 对同一模型同时挂浮动别名与带日期快照,并用 canonical_slug 声明同属一次发布。本项目转发时推理默认开着,与 Max 变体同口径,故按 canonical_slug 的日期取同族最新快照的分;显式带日期戳的 id 仍用自己的分。家族只从带 -YYYYMMDD 的 canonical 派生。
主动探活面板(GET /api/models/probes,operator)只探仍被 blocked 负缓存锁住的 (渠道, 模型)。
三态判定:
- 成功 → 立即清该渠道的
blocked负缓存(唯一的提前恢复入口)。 failed(上游明确BLOCKED/INVALID)→ 保留负缓存并累加连续失败。inconclusive(额度、限流、网络、超时、空流、任何异常)→ 绝不禁用,并把连续失败归零。
探活与生产完全隔离:直连 provider.stream_chat,不写 usage_events、不动冷却表、不改健康度。结论落独立的 model_probes 表。面板不列出命中 MODEL_BLOCKLIST 的模型。
判死的模型同时从模型列表摘掉:failed 且仍被负缓存锁着时,只抽该 (渠道, 模型);负缓存过期即自动回列表。
人工接管(POST /api/models/probes/manual):接管后自动探活跳过该行,但不改负缓存。要立刻恢复点「立即探活」。后台周期由 MODEL_PROBE_MINUTES 控制(默认 0 = 关闭)。
(GET /api/upstream-status,管理员 / 操作员)聚合各提供方官方状态页,回答「这个报错是上游挂了,还是我这边凭证 / 额度问题」。
- 来源:OpenCode Zen(逐模型 TTFT / tps)、Kilo、Moonshot / Kimi、MiniMax、OpenAI、Anthropic、Together AI、DeepSeek。按
scope分两组:channel在前,model在后。 - 归一化六档:
operational/degraded/partial/major/maintenance/unknown。整体取最差源,unknown排最后。 - 只做解释、不做决策,不参与选号 / 熔断。
- 只存内存、不落盘;抓取由后台任务(
UPSTREAM_STATUS_MINUTES,默认 5 分钟)负责,单源失败只标该行。 - 版式先结论后明细:顶部概况条,下面按 scope 分两组,异常源默认展开;「需要关注」条把非正常源提前。组件明细超过 6 项时截断(异常项排前)。
- 不覆盖仅提供 HTML 的源(Groq、腾讯云、阿里云、华为云、SiliconFlow、Qoder、智谱、TRAE)。
凭证列表的 token 剩余列显示 access token 距到期还有多久,低于 TOKEN_EXPIRY_WARNING_SECONDS(默认 1 小时)标红。优先取上游 expires_at,缺失回落 JWT exp;都取不到显示 —,不猜本地 TTL。
侧栏在每个页面都在。多个不点进去就发现不了的待办态在这里显示成计数角标,由单个聚合端点 GET /api/nav-badges 返回,侧栏 60s 轮询。
| 导航项 | 门槛 | 角标口径 |
|---|---|---|
| 凭证管理 | operator+ | disabled 或 health < 0 的凭证数 |
| 运维告警 | admin | 仍在告警中的条数 |
| 任务与配置 | admin | 上次执行失败的任务数 |
| 上游状态 | operator+ | 异常源数(含维护中) |
| 用户管理 | admin | 有激活令牌的账号数 |
| API Key | 全员(仅本人) | 已过期或 7 天内到期的 Key 数 |
| 模型列表 | operator+ | 快照陈旧(布尔文字) |
- 按角色裁剪:后端只下发有权看的键,缺失 = 无权看(不是 0)。凭证角标不下发给 viewer。
- 只数「待办」:例如告警页
total含已恢复历史,角标只数未恢复的;任务用「上次失败」而非「连续失败 ≥ 阈值」。 - 凭证角标 ≠
/healthz的可用数:后者不看 health,额度耗尽的号仍算ready。 - 告警角标会自愈:
AlertTask启动时从库里未恢复的行重建集合。 - 模型目录陈旧:从未拉取不算陈旧;超过 2 倍
OPENROUTER_CATALOG_MINUTES未刷新才算。
时间范围默认最近 24 小时(另有 7 天 / 30 天 / 全部 / 自定义)。自定义给两个 datetime-local(按浏览器本地时区),可只填一边;起点不早于终点时整窗作废并提示。所有统计端点接受 until(epoch 秒,半开上界 [since, until))。
明细失败类型是受控枚举(后端 CONTROLLED_ERROR_TYPES):模型级冷却耗尽记 model_unavailable,凭证池真耗尽记 no_healthy_credential(对外错误码相同,仅统计口径区分)。
请求 ID:每个响应都带 X-Request-ID。入站合法则沿用,否则自生成;合法字符集 [A-Za-z0-9._:-]、长度 1–128。该 ID 落进 usage_events.request_id,明细时间列悬停可见。不透传到上游(各渠道出站头语义不同)。
Credit 消耗:CodeBuddy 是上游真值;TRAE 按官方单价推算并标 ≈(单价表在 src/provider/trae/pricing.py,未收录不推算)。历史可用 scripts/backfill_trae_credit.py 补齐。
成本(估算,≈):token × 公开刊例价,不是上游真实扣费。单价取自 OpenRouter 公开目录,按 (输入−命中)×输入价 + 命中×缓存价 + 输出×输出价 折美元,再按写入时生效的 USD_CNY_RATE 折人民币。
- 写入时定值:成本在写明细时算好落库,历史行不随价表或汇率重算。上线前历史两列为
NULL,可用scripts/backfill_cost.py --apply补齐。 - 匹配不到就不计:本地模型名按 OpenRouter
id/ 展示名等值匹配(命中不唯一就不配),聚合值null显示—,故成本是下限。渠道分发代号按显式等价规则归到基础模型(只登记实测过的 id,不做通用剥日期 / 剥区域尾缀)。
缓存命中率:命中 token ÷ 输入 token(1 位小数)。未上报或输入为 0 时显示 —。分组表与明细表把命中率与 token 放同一格。
输出速度(tok/s):token 总量 ÷ 生成时间总和,不是逐条比值再平均。两个口径分母相同、分子不同:正文速度用 output_tokens - reasoning_tokens,总产出速度含思考 token。前端并成一格显示 正文 / 总产出。只算经本服务路由且产出 ≥ 1 token 的成功请求。生成时间 = latency_ms - ttfb_ms;生成窗口 < 200 ms 或单条 > 1000 tok/s 的按不可测剔除。ttfb_ms 为 NULL 时不进主口径;某行 / 组在主口径下没有样本时回落估算口径(端到端耗时当分母,展示前置 ≈)。两口径都没样本时显示 —。
聚合速度只能从逐请求明细算,故只覆盖最近 90 天(总览卡片同样如此,其余卡片读小时汇总)。明细表给每条请求自己的速度,不回落估算口径,无生成窗口的行显示 —。速度列可点表头排序(sort=tps_visible / sort=tps)。
凭证额度列数字后的下箭头展开「变动流水」,记录两次额度探测之间的净变化,不是动作归因:签到、成长、对话都会改余额而上游不打日志,界面一律写「净变化」。首次探测只建基线;余额没变不记;余额变「未知」仍记但不填变化量;保留 90 天。
按账本分流:流水带 ledger 列(pool / credits)。CodeArts 记 credits,其余渠道记 pool。接口 GET /api/credentials/{id}/credit-events?ledger=(缺省 pool)。两本账量级与语义不同,必须分开读。
分组统计用 tabs 切四个维度(渠道 / 模型 / 用户 / 凭证),共用一张表(渠道 / 请求 / Credit / 成本 / Token / 速度 / 平均延迟 / P95 延迟)。端点 GET /api/stats/by-provider|by-model|by-user|by-credential。默认按请求数降序,点列头切换排序;合并列点列头弹菜单选子列。延迟分位可切 P50 / P90 / P95 / P99(默认 P95,nearest-rank,n 为有值样本数)。按用户 / 按凭证只对 admin 与 operator 展示。
口径差异:渠道 / 模型 / 用户三维读小时汇总(永久,最近 5 分钟尚未计入);凭证维度读明细表(只覆盖 90 天)。
延迟两类口径:只统计成功请求。平均延迟 = latency_sum ÷ 成功数(小时汇总);分位延迟从逐请求明细算(只覆盖 90 天)。ttfb_ms 为 NULL 的两列各自独立排名。无样本显示 —。
失败归因:分组表下方的面板,GET /api/stats/errors(operator+)按 (渠道, 模型, 错误类型) 聚合窗口内 ok=0 的明细,给失败次数与最近失败时刻(按失败数降序,limit ≤ 5000)。点行即钻取到明细并加「只看失败」(GET /api/stats/events?ok=0)。只在 admin/operator 下渲染。/metrics 的 upstream_errors_total 仍取进程内计数,两者不合并。
点行钻取:点分组表任意行,明细即按该行过滤(渠道 / 模型 / 凭证,用户维度走作用域参数),条件可叠加。GET /api/stats/events 接受 provider / model / credential_id(AND)与 ok=0。
明细表列合并:Token、延迟、速度、渠道 / 凭证各并成一列,默认值一眼可读、悬停展开子项。时间列拆成两行(时刻 / 日期)。点合并列头弹出下拉选子列排序(sort=cache_rate / tps_visible / tps / provider / credential)。
导出 CSV:GET /api/stats/events?format=csv(仅 admin / operator),按当前时间范围 + 钻取过滤导出。UTF-8 BOM、\r\n、时间 ISO 8601(UTC),对以 = + - @ 开头的文本单元格加前导单引号防公式注入。单次上限 10000 行,超出截断并带 X-Export-Truncated: 1。
实时流(SSE):GET /api/stats/events/stream,展示当前筛选范围内刚落库的请求。事件在 StatsCollector.record 事务提交后由进程内 LiveEventHub 广播,前端保留最近 50 条。
- 作用域与过滤同
/api/stats/events;换过滤会重建连接。 - 广播 payload 不含凭证昵称;仅 admin/operator 的连接在服务端按需补
credential_name。 - 每订阅者有界缓冲 200 条(满则丢最旧);并发上限 8,超出 429。
- 15 秒注释帧保活;
Cache-Control: no-store+X-Accel-Buffering: no。
历史数据偏小时用 python3 scripts/rollup_hourly.py --apply 重算(默认预览、写库前备份、幂等)。
列头可点排序:点一次用该列首次方向(数值降序、文本升序),再点切换。排序由后端完成,所有列表端点接受 sort(白名单键)与 order(asc / desc)。未知 sort 或非法 order 回落到默认排序,不报错。
适用:/api/credentials、/api/users、/api/api-keys、/api/audit、/api/alerts、/api/model-catalog、/api/credentials/{id}/credit-events、/api/credentials/{id}/accounts、/api/stats/by-*、/api/stats/events。
统一分页:第 N / M 页、每页 20 / 50 / 100(默认 50)。扁平列表由后端分页——端点接受 limit + offset 并返回筛选后的 total。/api/model-catalog 因排序键含派生字段,先内存排序再切片,另接受 q 与 only_available。
分组类表(凭证页按渠道分组、用量统计分组表)不分页。
额度探测、token 预刷新、每日签到、成长中心、活跃上报、明细清理、数据库备份、渠道模型列表刷新、模型目录刷新、运维告警、上游状态页聚合、模型主动探活共 12 类,由 TaskRunner 调度,失败互不影响。「任务与配置」页查看上次执行时间、最近结果与错误(30 秒刷新),运行态只存在于本次进程。
每个任务卡片带「立即运行」(admin + CSRF):绕过时点窗口但绕过不了开关;已在跑时回 busy=true 不排队。
渠道模型列表刷新(MODEL_CATALOG_MINUTES,默认 30)是兜底保鲜,保证没人访问时也会更新。
成长中心仅 CodeBuddy。周期默认 60 分钟(GROWTH_INTERVAL_MINUTES)。不可逆动作由 GROWTH_IRREVERSIBLE_ACTIONS=false 关停。
ACTIVITY_REPORT_ENABLED=true 时,每天 ACTIVITY_REPORT_HOUR(默认 10 点)整点窗口为每个账号补发一条 chat_request_send 事件续连登。与积分、调度无关。适用于 CodeBuddy 与国际站。
风险:官方条款禁止用脚本篡改活动数据,处罚为取消资格并追回礼品。默认关闭,开启前自行评估。上游改版即失效。缺少
userId时上游会静默丢弃;OAuth 凭证的user_id为空时回落取 JWTsub,仍缺失则跳过。
后台周期评估四类风险并落 alert_events(「运维告警」页,admin-only):
| 规则 | 触发条件 |
|---|---|
pool_empty |
可用数少于 ALERT_POOL_READY_MIN |
task_failed |
某任务连续失败达到 ALERT_TASK_FAILURES 次 |
token_expiring |
token 剩余寿命落在 ALERT_TOKEN_EXPIRY_HOURS 内 |
error_rate |
统计窗内失败占比达到 ALERT_ERROR_RATE_THRESHOLD 且样本足够 |
- 静默窗:同一
(规则, 对象)窗口内只落库 / 推送一次。 - 迟滞:连续命中若干轮才告警,连续未命中若干轮才确认恢复。恢复时给该
(规则, 对象)全部未恢复的行写resolved_at,并外推status="resolved"的 webhook。 - 重启自愈:迟滞计数进程内维护;「哪些告警未恢复」从库里重建。
- 人工处置:「标记恢复」按钮(admin + CSRF + 审计),只标该行。
- 保留期:同请求明细(默认 90 天)。
自动备份(BACKUP_INTERVAL_MINUTES,默认每日)把一致性快照写进 DATA_DIR/backups/,只保留最近 BACKUP_KEEP 份(默认 7)。
- 用
VACUUM INTO而不是复制文件(WAL 模式下直接cp会丢未 checkpoint 的事务)。 - 落盘即
PRAGMA integrity_check,不过则删掉半成品。产物0600、目录0700。 - 失败只记日志,不影响服务。
手动备份:
python3 scripts/backup_db.py # 备份到 DATA_DIR/backups/,保留 7 份
python3 scripts/backup_db.py --keep 14 # 保留 14 份(0 = 不清理)
python3 scripts/backup_db.py --out /path/dir恢复(先停服):
python3 scripts/restore_db.py data/backups/coding2api-20261014-030000.sqlite3 # 预览:只校验
python3 scripts/restore_db.py data/backups/coding2api-20261014-030000.sqlite3 --apply # 校验 → 安全副本 → 原子替换恢复流程:校验完整性 → 给现有主库做 .pre-restore-<时间戳> 副本 → 原子替换 → 删除 -wal/-shm 残留。恢复后重启服务。
完整项见 src/config.py(权威),每一项都已透传到 docker-compose.yml。容器里改监听地址用 HOST/PORT。
| 变量 | 默认 | 说明 |
|---|---|---|
APP_SECRET |
必填 | 凭证加密密钥,≥16 字符;丢失 = 凭证全部作废。拒绝低熵值 |
ADMIN_USERNAMES |
空 | 仅引导期:逗号分隔,首次启动提权为 admin |
USERS_FILE |
secrets/users.txt |
仅引导期:老式用户文件,启动时一次性导入 |
PUBLIC_BASE_URL |
http://127.0.0.1:8000 |
浏览器可达地址;TRAE 登录回调用;https 时下发 HSTS。不决定会话 Cookie 的 Secure |
DATA_DIR |
./data |
SQLite 与运行数据目录 |
DEFAULT_MODEL |
deepseek-v4.1-flash |
模型为空 / auto 时的默认 |
QUOTA_PROBE_MINUTES |
60 |
额度探测周期(下限 1) |
REFRESH_INTERVAL_MINUTES |
30 |
token 预刷新轮询周期(下限 5) |
MODEL_CATALOG_MINUTES |
30 |
渠道模型列表兜底刷新周期(下限 5) |
OPENROUTER_CATALOG_MINUTES |
1440 |
模型目录刷新周期(下限 60) |
OPENROUTER_MODELS_URL |
https://openrouter.ai/api/v1/models |
模型目录来源(匿名可读);改后需重启 |
USD_CNY_RATE |
6.70 |
成本估算汇率;可热更,只影响之后写入的请求 |
GROWTH_INTERVAL_MINUTES |
60 |
成长中心周期(下限 5) |
GROWTH_IRREVERSIBLE_ACTIONS |
true |
是否允许抽奖 / 连登兑换 / 开盲盒 / 消耗补登卡 |
ACTIVITY_REPORT_ENABLED |
false |
活跃上报,默认关闭 |
ACTIVITY_REPORT_HOUR |
10 |
活跃上报的本地整点窗口 |
CHECKIN_TIMES |
"" |
签到时点 HH:MM 逗号分隔;留空 = 全天每 10 分钟检查一次。可热更 |
CHECKIN_JITTER_MINUTES |
0 |
签到抖动(分钟),在时点之前随机提前。可热更 |
LOG_BUFFER_LINES |
500 |
服务端日志页缓冲行数;0 关闭该页。可热更 |
ALERT_ENABLED |
true |
运维告警开关 |
ALERT_WEBHOOK_URL |
空 | 告警 Webhook,逗号分隔(≤16,仅 http(s),拒内网 / 回环防 SSRF),带 X-Coding2API-Signature |
ALERT_INTERVAL_MINUTES |
5 |
告警评估周期 |
ALERT_SILENCE_MINUTES |
30 |
同 (规则, 对象) 静默窗;0 关闭 |
ALERT_FOR_TICKS |
1 |
进入迟滞轮数 |
ALERT_CLEAR_TICKS |
2 |
恢复迟滞轮数 |
ALERT_POOL_READY_MIN |
1 |
池耗尽阈值;0 关闭 |
ALERT_TASK_FAILURES |
3 |
任务连续失败次数阈值;0 关闭 |
ALERT_TOKEN_EXPIRY_HOURS |
24 |
token 剩余小时阈值;0 关闭 |
ALERT_ERROR_RATE_THRESHOLD |
0.5 |
错误率阈值;0 关闭 |
ALERT_ERROR_RATE_MIN_REQUESTS |
20 |
错误率最小样本数 |
ALERT_ERROR_RATE_WINDOW_MINUTES |
15 |
错误率统计窗 |
BACKUP_INTERVAL_MINUTES |
1440 |
备份周期(下限 60) |
BACKUP_KEEP |
7 |
备份保留份数;0 = 不清理 |
UPSTREAM_STATUS_ENABLED |
true |
上游状态页开关 |
UPSTREAM_STATUS_MINUTES |
5 |
状态抓取周期(下限 1) |
UPSTREAM_STATUS_TIMEOUT_SECONDS |
10.0 |
单源 HTTP 超时 |
MODEL_PROBE_MINUTES |
0 |
主动探活周期(0 = 关闭)。只探被 blocked 负缓存锁住的 (渠道, 模型);成功即清负缓存,其余一律不禁用。可热更 |
QUOTA_EXPIRY_WINDOW_SECONDS |
129600 |
主到期排序窗口;≤0 关闭 |
QUOTA_EXPIRY_SECONDARY_WINDOW_SECONDS |
604800 |
次到期窗口;≤0 关闭 |
CONVERSATION_STICKY_SECONDS |
3600 |
会话粘性 TTL。优先级:请求体会话标识 > 请求头 > 消息前缀指纹;pin 最高。≤0 关闭 |
MODEL_BLOCKLIST |
custom_model_*,*sub*agent*,summary,browser_use_*,file_search_agent,default,seed-code-pro-*,sagitta,aquila,glm-4.*,glm-5,glm-5-turbo,glm-5.0*,glm-5.1,glm-5v-*,kimi-k2.5,kimi-k2.6,kimi-k2-thinking,minimax-m2.5,deepseek-v3-*,hunyuan-*,hy3*,deepseek-v4-*,qwen3.7-*,auto,nemotron-3-*,glm-5.2*,nemotron-3*,lfm2*,north-mini-code,openrouter-models-router,fast,balanced,deep,primary,hy4-preview,inkling-small,minimax-m2.7,kimi-k2.*,minimax-m3,gpt-5.4,gpt-5.5,laguna*,qwen3-vl-235b,seed-2.1*,xing4.0-29b-a4,glyph-cluster,qwen3.8-flash,seed-evolving |
模型列表黑名单(fnmatch,仅影响展示),按归一后写法匹配 |
MODEL_FALLBACK_GROUPS |
"" |
跨渠道 fallback 组:组名=成员1,成员2;…。留空不启用 |
ALLOWED_HOSTS |
空 | Host 白名单,防 DNS rebinding |
ALLOW_TEST_HOST |
false |
放行 testserver,仅测试用 |
TRUST_PROXY |
false |
是否采信 X-Forwarded-For;仅「前恰好一层受信反代」时开 |
ENABLE_DOCS |
false |
是否暴露 /docs 与 /openapi.json |
DUMP_REQUEST_BODIES |
false |
把 /v1 原始请求体落盘(含对话内容,仅排查) |
LOG_LEVEL |
INFO |
日志级别;审计日志是 INFO,调到 WARNING 会一并关掉 |
HOST / PORT |
127.0.0.1 / 8000 |
监听地址与端口(compose 默认 0.0.0.0);PORT 同时决定宿主映射 |
CODEBUDDY_API_ENDPOINT |
https://copilot.tencent.com |
上游地址,改动须同步白名单 |
CODEBUDDY_ALLOWED_ENDPOINTS |
官方两站(见 compose) | 端点白名单,真实 Token 只发往白名单内 |
CODEBUDDY_INTL_API_ENDPOINT |
https://www.codebuddy.ai |
国际站地址 |
CODEBUDDY_INTL_ALLOWED_ENDPOINTS |
https://www.codebuddy.ai |
国际站白名单,刻意不含国内站 |
ZEN_API_ENDPOINT |
https://opencode.ai |
Zen 上游地址 |
ZEN_ALLOWED_ENDPOINTS |
https://opencode.ai |
Zen 白名单 |
ZEN_OPENCODE_VERSION |
1.18.0 |
门禁伪装 UA 版本(低于阈值会被 426 拒绝) |
KILO_API_ENDPOINT |
https://api.kilo.ai/api/gateway |
Kilo 上游地址 |
KILO_ALLOWED_ENDPOINTS |
https://api.kilo.ai/api/gateway |
Kilo 白名单 |
QODER_API_ENDPOINT |
https://openapi.qoder.com.cn |
Qoder openapi(额度 / 登录);国际站 https://openapi.qoder.sh |
QODER_GATEWAY_ENDPOINT |
https://gateway.qoder.com.cn |
Qoder 推理网关;国际站 https://api1.qoder.sh |
QODER_ALLOWED_ENDPOINTS |
国内 openapi+gateway + 国际站(见 compose) | Qoder 白名单 |
CODEARTS_API_ENDPOINT |
https://snap-access.cn-north-4.myhuaweicloud.com |
CodeArts snap 引擎 |
CODEARTS_ALLOWED_ENDPOINTS |
snap 引擎 + STS + 门户(见 compose) | CodeArts 白名单 |
OFFICEACE_API_ENDPOINT |
https://agentarts.cn-southwest-2.myhuaweicloud.com |
OfficeAce 控制面 |
OFFICEACE_ALLOWED_ENDPOINTS |
https://agentarts.cn-southwest-2.myhuaweicloud.com |
只需控制面(模型网关由上游下发) |
OFFICEACE_CHAT_MIN_INTERVAL |
0 |
OfficeAce 聊天最小间隔 |
PROVIDER_PROXIES |
"" |
按渠道出站代理;启动期项 |
CODEBUDDY_CHAT_MIN_INTERVAL |
5 |
CB 聊天节流(按凭证分桶) |
CODEBUDDY_INTL_CHAT_MIN_INTERVAL |
5 |
CB 国际节流,独立 |
ZEN_CHAT_MIN_INTERVAL |
0 |
Zen 节流 |
KILO_CHAT_MIN_INTERVAL |
0 |
Kilo 节流 |
QODER_CHAT_MIN_INTERVAL |
5 |
Qoder 节流 |
CODEARTS_CHAT_MIN_INTERVAL |
5 |
CodeArts 节流 |
CODEARTS_MAX_CONCURRENCY |
3 |
CodeArts 每账号在途并发上限;0 关闭 |
CODEARTS_REQUEST_WINDOW_SECONDS |
60 |
CodeArts 账号滑动窗口;0 关闭 |
CODEBUDDY_SANITIZE_CHANNEL_MARKERS |
true |
出站正文命中「伪装官方客户端」指纹串时替换(上游 11128 风控) |
REFRESH_SKEW_HOURS |
24 |
token 到期前该窗口内预刷新;短寿命渠道自行封顶 |
TOKEN_EXPIRY_WARNING_SECONDS |
3600 |
token 到期预警阈值;≤0 关闭。纯展示 |
PACER_MIN_SECONDS / PACER_MAX_SECONDS |
5 / 20 |
全局节流随机等待区间 |
AUTO_CONTINUE_MAX |
10 |
finish_reason=length 时同凭证自动续写次数;0 关闭 |
CONTEXT_COMPRESS_ENABLED |
true |
按模型输入上限裁剪过长对话 |
CONTEXT_COMPRESS_RESERVE_TOKENS |
4096 |
为回复预留的输出 token |
CONTEXT_COMPRESS_MIN_KEEP_MESSAGES |
4 |
无论多长都保留的最近消息条数 |
CONTEXT_COMPRESS_SAFETY_RATIO |
0.95 |
压缩预算占模型上限的比例 |
UPSTREAM_COMPLETE_TIMEOUT_SECONDS |
600 |
非流式聚合整体超时;≤0 关闭 |
STREAM_FIRST_EVENT_TIMEOUT_SECONDS |
0 |
首事件超时(出帧前可换号)。默认必须保持关闭,推荐 60。可热更 |
STREAM_IDLE_TIMEOUT_SECONDS |
0 |
流内空闲超时(已出帧只断流不换号),推荐 300。可热更 |
SHORT_COOLDOWN_WAIT_SECONDS |
0 |
全池短冷却时等待续上;0 = 快速失败。可热更 |
NONSTREAM_KEEPALIVE_SECONDS |
0 |
非流式出口 N 秒未完成则转 chunked 吐空白心跳(JSON 前导空白合法);0 = 关闭,仅在 CDN 后需要,推荐 15。可热更 |
MODEL_ALIASES |
"" |
自定义别名;留空不启用。可热更 |
MAX_INFLIGHT_PER_CREDENTIAL |
0 |
单账号在途上限;0 = 不限。600s TTL 兜底 |
METRICS_ENABLED |
true |
是否暴露 /metrics(不鉴权)。可热更 |
REQUEST_LOG_ENABLED |
true |
请求级结构化日志(logfmt);不写 query / 请求体 / 用户名。可热更 |
FORCE_DISABLE_THINKING |
false |
出站剥离思考内容,只过滤响应不改请求。可热更 |
REASONING_EFFORT_MAP |
"" |
出站档位映射 渠道=from:to;*=from:to(渠道取 KNOWN_PROVIDERS);目标写 none = 删键。可热更 |
ANTHROPIC_THINKING_EFFORTS |
"" |
Anthropic thinking.budget_tokens → 上游 reasoning_effort。可热更 |
上表中带「可热更」语义的 60 项可不改 .env、不重启,直接在「任务与配置」页修改(完整列表见该页)。
要点:优先级 DB 覆盖值 > .env——改过后 .env 对该项不再生效,页面标「DB 覆盖」,点「恢复默认」删掉覆盖行才回落 .env。值存 runtime_settings 表,新增可热更项无需迁移;白名单外的 key、非法类型 / 越界值写入前即拒。启动期项(APP_SECRET / PORT / DATA_DIR / USERS_FILE / OPENROUTER_MODELS_URL / 上游端点白名单)不在白名单。接口 GET /api/settings 读快照、PUT /api/settings 写(admin + CSRF),传 null 恢复默认。该页同时展示后台任务运行态(GET /api/tasks,进程内,30 秒刷新)。
-
挂载目录属主:容器内以 uid 1001(
appuser)运行,./data与./secrets必须可写 / 可读:mkdir -p data secrets && sudo chown -R 1001:1001 data secrets -
时区:镜像默认
TZ=Asia/Shanghai,.env里的TZ会透传。签到 / 额度周期按「今天 9 点」判断,务必与宿主一致。 -
端口:compose 里
PORT同时决定容器内监听端口与宿主机映射端口。 -
改代码后重建镜像:
docker compose up -d不带--build会用本地已有镜像,跑旧代码。改src/或前端后务必docker compose up -d --build。
改动 src/ 后必须重启进程,否则出现「新前端 + 旧后端」错配:页面能打开,但新端点全部 404。前端不需重启(后端每次请求现读 web/dist),后端不是。
docker compose up -d --force-recreate # Docker / compose
sudo systemctl restart coding2api # systemd确认升级已生效:
sqlite3 data/coding2api.sqlite3 "PRAGMA user_version; SELECT username, role, enabled FROM users;"
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/api/users # 期望 401;404 = 旧后端后端把前端产物放在 / 直接服务(每次请求现读磁盘),只改前端时构建完刷新即可。localhost:5173 是 Vite 开发态,两者可并存。
./scripts/build-web.sh # 仅在产物过期时构建
./scripts/build-web.sh --force # 无条件重建
web/.npmrc不能删:它关掉了auto-install-peers(@lobehub/icons会把@lobehub/ui/antd拖进来)。该设置与 lockfile 的settings.autoInstallPeers强耦合,Dockerfile也必须COPY web/.npmrc,否则--frozen-lockfile报ERR_PNPM_LOCKFILE_CONFIG_MISMATCH。
部署相关操作记在本地文档:进程管理方式、路径与工具版本见
docs/local-environment.md(已 gitignore)。
家宽在 CGNAT 后没有可入站地址,公网入口只能由内网侧主动连出。Cloudflare Tunnel 免费版会把大陆流量绕到洛杉矶(实测 1.1–2.4s、丢包约 60%);「境外 VPS 做入口 + frp 隧道回源」可降到 80–150ms。只改入站链路,出站仍走 PROVIDER_PROXIES。
客户端 ──443/TLS──> VPS(nginx) ──frp 隧道──> 家中 Mac:8000
产物在 deploy/frp/、deploy/nginx/、deploy/systemd/frps.service、deploy/launchd/com.coding2api.frpc.plist(frp v0.71.1 TOML 格式)。
VPS 侧:
sudo apt install -y nginx certbot python3-certbot-nginx
sudo install -m 755 frps /usr/local/bin/frps # 从 frp release 解出
sudo useradd --system --no-create-home --shell /usr/sbin/nologin frps
sudo install -d -m 755 /etc/frp && sudo install -m 644 deploy/frp/frps.toml /etc/frp/
openssl rand -hex 32 | sudo tee /etc/frp/frps-token >/dev/null
sudo chmod 600 /etc/frp/frps-token && sudo chown frps:frps /etc/frp/frps-token
sudo install -m 644 deploy/systemd/frps.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now frps
sed 's/__TUNNEL_DOMAIN__/ai-api.example.com/g' deploy/nginx/coding2api.conf \
| sudo tee /etc/nginx/sites-available/coding2api.conf >/dev/null
sudo ln -sf /etc/nginx/sites-available/coding2api.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d ai-api.example.comDNS 把域名 A 记录指向 VPS 公网 IP;境外 VPS 无需 ICP 备案。
家中 Mac 侧:
brew install frp
./scripts/install-frpc.sh --server <VPS 公网地址> --token <与 VPS 一致的 token>
launchctl print gui/$(id -u)/com.coding2api.frpc | grep -E 'state|pid'
./scripts/install-frpc.sh --uninstallfrpc.toml 用 {{ .Envs.FRP_SERVER_ADDR }} 模板取值;地址与 token 由 plist 注入,token 落在 secrets/frpc-token(600,已 gitignore)。
.env 要同步三处:
| 变量 | 值 | 漏了会怎样 |
|---|---|---|
ALLOWED_HOSTS |
追加隧道域名 | 隧道侧全部 400 invalid host header |
PUBLIC_BASE_URL |
https://<隧道域名> |
TRAE 登录回调基址错、不下发 HSTS |
HOST |
保持 127.0.0.1 |
隧道回源到本机回环即可 |
TRUST_PROXY:VPS 上恰好一层 nginx 可置 true,让 Key 的 allowed_ips 看到真实 IP。若仍需局域网直连入口,保持 false。
应用只写 stdout / stderr,不写文件、不自行轮转。
| 部署形态 | 日志到哪 | 轮转 | 需做什么 |
|---|---|---|---|
| Docker / compose | docker logs coding2api |
json-file(10m × 5) | 无需操作 |
| Linux(systemd) | journalctl -u coding2api |
journald 自带 | 无需操作 |
| Linux(非 systemd) | 重定向到文件 | logrotate |
sudo cp deploy/logrotate/coding2api /etc/logrotate.d/ |
| 裸跑 | 终端 stderr | 无 | 自行重定向并轮转 |
级别用 LOG_LEVEL(默认 INFO)。审计日志是 INFO 级,调到 WARNING 会一并关掉。
每个业务请求(/api/*、/v1/*、/authorize)额外写一行 logfmt:
event=request request_id=9f2c… method=POST route=/v1/chat/completions status=200 duration_ms=1234
route是路由模板(/api/credentials/{credential_id}),资源 ID 不进日志;未匹配到路由时回落为清洗过的原始路径。- 不写 query string、请求体、响应体、请求头、用户名;身份靠
request_id去usage_events关联。 request_id与响应头X-Request-ID同值。- 级别:
5xx记 WARNING,其余 INFO。 - 不记静态资源与探针(
/health、/healthz、/metrics)。 - uvicorn 自带 access log 已关闭。
- 开关
REQUEST_LOG_ENABLED(默认true,可热更)。
容器里
data/dumps/(DUMP_REQUEST_BODIES=true写入)不在 docker 日志体系内,应用自行保持最多 200 份。
# 后端:lint + 测试(行/分支覆盖门槛 100%)
uv run ruff check src tests scripts
uv run pytest -q --cov=src --cov-report=term --cov-fail-under=100
# 前端
cd web
pnpm exec tsc --noEmit
pnpm exec vitest run
pnpm build| 文档 | 内容 |
|---|---|
PROPOSAL.md |
目标与非目标、上游可行性、架构、数据模型、安全边界 |
TECHNICAL.md |
技术栈、模块规格、Provider 协议、请求时序、测试策略 |
diagrams/coding2api-architecture.html |
系统架构图 |
diagrams/coding2api-request-sequence.html |
请求主链路时序图 |
diagrams/coding2api-credential-lifecycle.html |
凭证调度状态机 |