最小化的 OpenAI 兼容中转站。网关跑在 Cloudflare Workers 上,模型后端既可以是任意 OpenAI 兼容服务,也可以是通过 Cloudflare Tunnel 接回来的本机 Cursor SDK 容器。
OpenAI 客户端
│ Authorization: Bearer sk-ro-...
▼
Worker (hashneuron.space 控制台 + api.hashneuron.space API)
│ 鉴权 / 限流 / 配额 / 渠道选择 / 用量记账
│ ├── D1 令牌、渠道、请求日志
│ └── Durable Object 每令牌的 RPM 计数
│
├──► 渠道 A:https://api.openai.com/v1 直连 OpenAI 或任何兼容端点
└──► 渠道 B:https://bridge.hashneuron.space/v1
│ Cloudflare Tunnel
▼
本机 Docker 容器(Node + @cursor/sdk)──► Cursor API
两条链路对客户端完全一致:都是 /v1/chat/completions。对 Worker 来说本机 bridge
也只是一个普通渠道,网关侧没有任何特殊分支。
https://hashneuron.space/ 是一个仿 platform.openai.com 的控制台,由 Worker 的静态资源
托管,和 API 同源,所以前端直接用相对路径调 /v1 和 /admin,没有跨域问题。
控制台支持 GitHub 和 Google 授权登录,另外保留 ADMIN_TOKEN 作为脚本通道和救急入口。
会话是 HMAC-SHA256 签名的 cookie(ro_session={userId}.{exp}.{sig},HttpOnly / Secure /
SameSite=Lax,30 天),服务端不存 session 记录,只在校验时回查用户是否仍被放行。签名
密钥取 SESSION_SECRET,没配就退回 ADMIN_TOKEN——也就是说换掉 ADMIN_TOKEN 会让所有
会话立刻失效。
授权登录即注册,不需要审批,和常规的第三方登录一致。
账号和登录方式是分开的:users 是账号,identities 记录绑了哪些 provider。登录时按已验证
邮箱认人,所以同一个人用 GitHub 还是 Google 进来都落在同一个账号上,看到的是同一批密钥和
用量。只有 provider 明确标记为已验证的邮箱才参与关联,否则别人拿你的邮箱注册一个第三方账号
就能顶掉你的账号。拿不到邮箱的情况下会各自独立成账号。
安全性靠数据隔离而不是准入门槛:
- 第一个登录的人是 owner,能看全量数据,并且独占 Channels、Users、Settings 三个页面。 所以部署完应该尽快自己登一次把 owner 占住。
- 其余用户是普通成员,只看得到自己签发的密钥和自己产生的用量,上游渠道对他们完全不可见 ——就像 OpenAI 不会把自己的算力供应商暴露给用户一样。
- owner 可以在 Users 页面停用或删除某个账号。
普通成员默认使用 Free 套餐,限制按账号聚合,创建多把 API Key 不能绕过:
- 5 RPM
- 每日 200,000 tokens(UTC 零点重置)
- 最多 2 把 API Key
- 同时 1 个请求
owner 不受这些限制,并可在 Users 页把成员改为自定义额度。单把 Key 自身的 RPM、总配额和 模型白名单仍然保留;请求必须同时满足 Key 限制和用户限制。
配置登录方式(Settings 页面,owner 可见):
- GitHub — 点「一键创建 GitHub App」,跳到 GitHub 确认后凭据自动写回;也可以自己建
OAuth App 再把 Client ID / Secret 填进来。回调地址
https://<域名>/auth/github/callback。 - Google — 在 Google Cloud Console 建 OAuth 2.0 客户端(类型选 Web application),
授权重定向 URI 填
https://<域名>/auth/google/callback,然后把 Client ID / Secret 填进来。
凭据也可以走 Worker secret,优先级高于数据库里的配置,配了之后控制台不允许再改:
npx wrangler secret put SESSION_SECRET
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put GOOGLE_CLIENT_ID
npx wrangler secret put GOOGLE_CLIENT_SECRET/admin/* 同时认会话 cookie 和 ADMIN_TOKEN。cookie 是 SameSite=Lax,写操作再校验一次
同源;/v1/* 才开放 CORS,/admin 与 /auth 保持同源。
- Chat — 模型下拉按 Composer / Claude / GPT / Gemini / Grok 分组,列出所有渠道 声明的模型;支持流式、思考过程折叠、system prompt、temperature、max_tokens、 reasoning_effort,回复下方显示本次真实 token 消耗。
- API keys — 签发、停用、删除自己的令牌,明文只在创建后弹窗里出现一次。
- Usage — 请求数 / 错误数 / token 用量 / 平均延迟,按模型统计与最近请求明细。
- Channels(owner)— 增删改上游、连通性测试,以及一键同步上游模型列表。
- Users(owner)— 停用 / 提权 / 删除控制台用户。
- Settings(owner)— 配置 GitHub 与 Google 登录。
Chat 页面不会拿管理口令去调模型,而是自动签发一把名为 console 的普通客户端令牌,
走的是和外部客户端完全相同的 /v1 链路,因此限流、配额、日志都照常生效。
对外(OpenAI 兼容)
| 端点 | 说明 |
|---|---|
GET /health |
探活 |
GET /v1/models |
汇总所有启用渠道的模型 |
POST /v1/chat/completions |
支持 stream: true(SSE)与非流式 |
POST /v1/completions |
透传 |
POST /v1/embeddings |
透传 |
POST /v1/responses |
透传 |
中转站自身
- 自签发令牌
sk-ro-...,数据库只存 SHA-256,上游密钥不下发给客户端 - 每令牌的 RPM 限流、token 配额、模型白名单、有效期、启停
- 每用户聚合的 Free 套餐限流、每日 token 配额、Key 数与并发限制
- 多渠道:优先级 + 权重随机,失败自动转移(最多 3 次),4xx 不重试直接回传
- 模型别名映射,例如把
gpt-4o-mini指到某渠道的实际模型 - 用量记账:优先用上游真实
usage,拿不到时估算并在日志标记estimated - 请求日志与统计,定时任务按保留天数清理
未做(有意的边界):多租户计费、Web 管理后台、n>1 / logprobs 这类 Cursor 侧对不上的参数。
走 Cursor bridge 时注意:SDK 是 agent 运行时而不是裸模型接口,每次请求都会带上它自己的 系统提示,一句"你好"实测也要 11k 左右的 prompt tokens。配额按这个量级估算。
npm install
npx wrangler login
# 建库,把返回的 database_id 填进 wrangler.jsonc
npx wrangler d1 create routeopen
npx wrangler d1 migrations apply routeopen --remote
npx wrangler secret put ADMIN_TOKEN # 管理 API 口令
npx wrangler deploy自定义域名在 wrangler.jsonc 的 routes 里,默认 api.hashneuron.space。
- 在 Cloudflare Zero Trust → Networks → Tunnels 新建隧道,拿到
TUNNEL_TOKEN, public hostname 配成bridge.hashneuron.space→http://localhost:8080。 走 API 建隧道的话面板不会替你建 DNS,要自己补一条CNAME bridge → <tunnel-id>.cfargotunnel.com(proxied), 对应的 API token 需要 Zone → DNS → Edit 权限。 - 准备环境变量:
cd bridge
cp .env.example .env
# 填 CURSOR_API_KEY、TUNNEL_TOKEN,并生成 BRIDGE_TOKEN:
openssl rand -hex 32- 一条命令拉起全部(dockerd → bridge 容器 → cloudflared 隧道):
./local-up.sh # 可重复执行,已在跑的组件会跳过
./local-down.sh # 停 bridge 与隧道,保留 dockerd
./local-down.sh --all # 连 dockerd 一起停这台机器没有 systemd,所以脚本用 nohup 拉起进程并把 PID 记在 logs/*.pid,
容器本身靠 compose 的 restart: unless-stopped 保活。停止按 PID 文件进行,
不用 pkill -f:模式匹配会误伤命令行里恰好含有该字符串的其他进程。
日志在 bridge/logs/:dockerd.log、cloudflared.log,容器日志用
docker logs routeopen-bridge。
CURSOR_API_KEY 只存在本机,不进 Cloudflare。隧道是公网可达的,所以 bridge 强制
校验 BRIDGE_TOKEN,缺少就拒绝启动。
bridge 默认允许 Cursor run 最长执行 90 秒。客户端断开或超时后会调用 SDK 的
run.cancel() 并释放并发槽,避免已超时的请求继续占住队列。Worker 等待 bridge
首个响应头的时间为 100 秒,略长于 run 超时,以便把明确错误返回给客户端。
这套东西跑在一台出网受限的机器上,几个结论值得记下:
- Cursor SDK 模型请求直连。Node 容器不注入
HTTP_PROXY/HTTPS_PROXY; SSH SOCKS 长连接会让本地运行时的Agent.create()卡住,而且代理出口也不能解除 Cursor 账号级的 GPT / Claude 区域限制。 - 镜像构建不联网。容器内构建阶段不经过 SSH 隧道,镜像里跑
apt-get或npm install会直连外网挂住。所以Dockerfile只做拷贝,依赖先在宿主机npm install。 ripgrep 和沙箱助手由@cursor/sdk-linux-x64提供,不需要 apt。 - 拉基础镜像要让 daemon 走代理。BuildKit 会绕过 daemon 的
proxies配置自行做 DNS 解析,所以先docker pull node:24-slim(daemon 自己的拉取器认代理), 再用DOCKER_BUILDKIT=0 docker build复用本地镜像。bridge/docker-daemon.local.json是一份不含国内镜像源、带 SOCKS 代理的 daemon 配置。 - cloudflared 跑在宿主机,因为拉
cloudflare/cloudflared镜像同样会失败, 而系统里已经装了这个二进制。 - 本机可能有 TLS 拦截。直连
https://api.hashneuron.space会拿到企业代理签发的 证书而报unable to get local issuer certificate;用curl -x socks5h://127.0.0.1:10808验证才准。这只影响本机验证,不影响线上服务。
GW=https://api.hashneuron.space
ADMIN="Authorization: Bearer $ADMIN_TOKEN"
# 把本机 bridge 注册成渠道,api_key 就是 BRIDGE_TOKEN
curl -X POST $GW/admin/channels -H "$ADMIN" -H 'content-type: application/json' -d '{
"name": "cursor-local",
"base_url": "https://bridge.hashneuron.space/v1",
"api_key": "<BRIDGE_TOKEN>",
"models": "composer-2.5",
"priority": 10
}'
# 别手写模型清单,直接从 bridge 同步。bridge 可用 ENABLED_MODELS 过滤
# Cursor 返回但当前账号无法调用的目录项。
curl -X POST $GW/admin/channels/<channel-id>/sync-models -H "$ADMIN"
# 需要直连 OpenAI 时再加一个渠道
curl -X POST $GW/admin/channels -H "$ADMIN" -H 'content-type: application/json' -d '{
"name": "openai",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-...",
"models": "gpt-4o,gpt-4o-mini"
}'
# 签发客户端令牌,明文只在这次响应里出现
curl -X POST $GW/admin/tokens -H "$ADMIN" -H 'content-type: application/json' \
-d '{"name":"my-laptop","rpm":60,"quota_tokens":0}'客户端照常用:
curl $GW/v1/chat/completions \
-H "Authorization: Bearer sk-ro-..." \
-H 'content-type: application/json' \
-d '{"model":"composer-2.5","stream":true,"messages":[{"role":"user","content":"你好"}]}'需要 Authorization: Bearer $ADMIN_TOKEN,或者一个已放行用户的会话 cookie。
| 方法与路径 | 说明 |
|---|---|
GET /admin/me |
当前调用者身份与是否 owner |
GET /admin/users |
列出控制台用户(owner) |
PATCH/DELETE /admin/users/:id |
停用 / 提权 / 删除(owner) |
GET /admin/settings/oauth |
登录方式配置状态,密钥不回传(owner) |
PUT/DELETE /admin/settings/oauth/:provider |
写入 / 清除 OAuth 凭据(owner) |
GET/POST /admin/tokens |
列出 / 签发令牌,成员只看得到自己的 |
PATCH/DELETE /admin/tokens/:id |
改配额限流状态 / 删除,成员只能动自己的 |
GET/POST /admin/channels |
列出 / 新增渠道(owner) |
PATCH/DELETE /admin/channels/:id |
修改 / 删除渠道(owner) |
POST /admin/channels/:id/test |
探测渠道可达性(owner) |
POST /admin/channels/:id/sync-models |
拉取上游模型列表写回渠道(owner) |
GET /admin/logs?limit=&token_id= |
请求日志,成员只看得到自己的 |
GET /admin/stats?hours=24 |
汇总与按模型统计,成员只统计自己的 |
Worker(wrangler.jsonc 的 vars 与 secret)
| 名称 | 说明 |
|---|---|
ADMIN_TOKEN |
secret,管理 API 口令,同时是会话签名的兜底密钥 |
SESSION_SECRET |
secret,会话 cookie 的 HMAC 密钥 |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET |
可选,配了就不能在控制台里改 |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
同上 |
DEFAULT_MODEL |
客户端未指定模型时的默认值 |
UPSTREAM_TIMEOUT_MS |
等待上游响应头的上限,默认 30000,不限制流式正文时长 |
LOG_RETENTION_DAYS |
日志保留天数,定时任务按此清理 |
OPENAI_API_KEY / OPENAI_BASE_URL |
可选,数据库没配渠道时的兜底上游 |
bridge 见 bridge/.env.example。
npx wrangler d1 migrations apply routeopen --local
node scripts/mock-upstream.mjs # 假上游,不消耗真实额度
npx wrangler dev --port 8890把渠道 base_url 指向 http://127.0.0.1:8788/v1 即可端到端验证转发、流式和记账。