mirasim-relay-bridge

agent
Security Audit
Warn
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 6 GitHub stars
Code Pass
  • Code scan — Scanned 2 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

把 Mirasim 内置的 Anthropic 反代接给独立 claude CLI 和 Zed ACP agent — 动态端口发现,不复制凭证

README.md

mirasim-relay-bridge

Mirasim 内置的 Anthropic 反代接出来,让独立的 claude CLI
Zed 的 ACP agent 也能走同一条链路 —— 不复制凭证、不碰 token 刷新、不写死端口。

macOS only(依赖 lsof 的行为和 macOS 上的进程模型)。

前提比你想的严格:反代不是应用级常驻的。Mirasim 为每次 agent run 起一个代理,
run 结束就 dispose()。所以光开着 Mirasim 不够 —— 得有一个正在跑的 agent 会话
代理才存在。详见限制


问题

Mirasim 是个 Electron 应用,它把 Claude Code 包在自己的反代后面:启动 agent 会话时,往子进程注入

ANTHROPIC_BASE_URL=http://127.0.0.1:<proxyPort>
ANTHROPIC_AUTH_TOKEN=mirasim-relay-managed-credential

这个本地端点会把请求转发到官方 relay,并在转发时换上你账号的真实托管凭证。

问题是这套注入只对它自己拉起的进程生效。你在终端里直接敲 claude、或者让 Zed 通过 ACP
拉起 Claude,走的都是原生登录态,跟 Mirasim 的额度和记录完全是两条线。

这个仓库把那条链路接出来。


排查思路

值得单独写出来的是过程,而不是结论 —— 结论就三个脚本。

1. 先看 env,确认本地端点存在

在 Mirasim 拉起的会话里 env | grep -i anthropic,能看到 ANTHROPIC_BASE_URL 指向
127.0.0.1 的某个高位端口,ANTHROPIC_AUTH_TOKEN 是个占位符字符串(不是真 key)。
占位符这一点很关键:说明鉴权不是由客户端负责的,本地端点会自己换凭证。

2. 确认本地端点不做鉴权

curl http://127.0.0.1:<port>/v1/messages \
  -H 'content-type: application/json' -H 'x-api-key: whatever' \
  -d '{"model":"...","max_tokens":8,"messages":[{"role":"user","content":"hi"}]}'

x-api-key: whatever 就能拿到正常回复 —— 它忽略传入的 token,只认「请求来自本机」。
所以任何本机进程指过去就能用,这是整个方案成立的前提。

3. 找上游反代

lsof 定位监听方是应用主进程,再去 app bundle 里 grep 编译进去的常量:

lsof -nP -iTCP:<port>
grep -ohE 'https://[a-zA-Z0-9._-]+' /Applications/Mirasim.app/Contents/Resources/server.cjs \
  | sort -u | grep -i relay

得到 https://mirasim-relay.mirofish.ai,以及解析逻辑:环境变量 RELAY_BASE_URL / RELAY_URL
优先,否则用编译进去的常量(release 版写死,只有 dev build 能覆盖)。

完整链路:

claude / Zed ACP
      ↓  ANTHROPIC_BASE_URL
127.0.0.1:<proxyPort>          ← Mirasim 主进程,在这里换上真实托管凭证
      ↓
https://mirasim-relay.mirofish.ai
      ↓
上游模型服务

4. 试过但放弃的路:直连 relay

拿配置文件里的托管 token 直接打 relay,401 invalid user token

一开始我以为是 token 过期(它确实有 exp,靠 refreshToken 续期)。但等应用刷新出一个还有
47 分钟才过期
的新 token 再试,依然 401 —— 所以过期不是原因。回去翻 bundle 才看到真正的门槛:

grep -ohE "'x-mirasim-[a-z0-9-]+'" server.cjs | sort -u
# x-mirasim-device  x-mirasim-nonce  x-mirasim-sig  x-mirasim-ts  x-mirasim-token …

relay 要的是签名请求 —— 时间戳 + nonce + 设备标识 + HMAC 签名。光有 bearer token 没用。
要直连就得把它的签名协议重新实现一遍,既脆弱又没必要。

顺带一个结论:不要去调登录后端的 /auth/refresh。服务端一旦轮换 refreshToken,应用自己存的
那份就作废了,代价是被强制重新登录。为了省一个进程依赖去换「可能把你踢下线」的风险,不划算。

所以最终方案是走本地端点,签名和凭证刷新都继续交给应用 —— 代价是必须让 Mirasim 有活着的会话


三个设计决策

端口必须动态发现

proxyPort 是运行时分配的,而且跟着 agent run 的生命周期走

// server.cjs 里的形状(反混淆后)
finally { await proxy.dispose(); }
return { exitCode, sessionId, proxyPort: proxy.port, … }

同时开两个会话就有两个端口,会话结束端口就消失,应用重启全变。所以任何写死端口的配置(包括写进
settings.jsonenv)都会失效。

判别哪个端口是反代,用 GET /v1/models:反代返回 JSON 模型列表,应用自己的 web UI 端口返回
HTML,shell 端点返回 401 bad shell token零 token 开销,比发一个真 prompt 去试要好。

(早期版本假设「应用启动时会建一个常驻代理,端口号最小的那个活得最久」—— 这是错的,压根没有
常驻代理。现在只是按端口号顺序取第一个能用的。)

不用 pgrep

第一版用 pgrep -f Mirasim 找主进程,结果只匹配到 helper 进程,主进程匹配不上(macOS 上
pgrep -f 读不到它的完整 argv)。改成从 lsof 的监听套接字反推,一步到位,顺便直接拿到端口。

不用 npx -y 拉 ACP 适配器

Zed 的 registry 条目是用 npx 按需拉 @agentclientprotocol/claude-agent-acp。包装脚本里照抄
这个做法会踩两个坑:npx 的缓存目录在受限环境下可能写不进去(ENOENT),以及每次启动都要付
一次包解析开销。改成 npm install --prefix 装到固定位置、直接 exec node <bin>

还有一条硬约束:ACP 的 stdout 是 JSON-RPC 通道,包装脚本往 stdout 写任何一个字节都会把协议
弄坏。所有诊断输出必须走 stderr。


安装

git clone https://github.com/<you>/mirasim-relay-bridge
cd mirasim-relay-bridge
./install.sh

装三个脚本到 ~/.local/bin,装 ACP 适配器到 ~/.local/share/mirasim-acp,然后打印 Zed 配置片段。
幂等,可反复跑。

脚本 作用
mirasim-relay-port 打印反代端口,找不到则退出 1。另外两个脚本都调它
claude-mira claude CLI:发现端口 → 注入 env → exec claude "$@"
claude-acp-mira 包 ACP 适配器,给 Zed 用

用法

CLI

claude-mira                          # 交互式
claude-mira -p 'hello'               # 一次性
claude-mira --model claude-opus-5    # 参数原样透传
MIRA_RELAY_PORT=62873 claude-mira    # 手动指定端口,跳过发现
MIRA_WAIT_SECS=30 claude-mira        # 等最多 30s 让代理出现

想让交互式 shell 里的 claude 默认走反代,加到 ~/.zshrc

[[ -z ${MIRASIM_INSTANCE_KEY:-} ]] && alias claude='claude-mira'

那个守卫是为了别在 Mirasim 自己拉起的会话里套第二层(那边 env 已经注入好了,再发现一次可能挑到
别的会话的端口)。逃生口是 command claude(或 \claude),走原生登录态。

alias 只作用于交互式 shell,所以脚本、IDE 插件、其他工具里的 claude 调用不受影响 —— 这是有意的。

Zed

并进 ~/.config/zed/settings.jsonagent_servers重启 Zed

"claude-mira": {
  "type": "custom",
  "command": "/Users/<you>/.local/bin/claude-acp-mira",
  "args": [],
  "env": {}
}

建议保留原来的 claude-acp registry 条目 —— 那是你的原生登录态逃生口,两个可以并存,在 agent
面板里自由切。

注意 Zed 的 agent_servers.env 只能写静态值,所以不能在那里直接写 ANTHROPIC_BASE_URL
端口是动态的。包一层脚本正是为了这个。

验证

python3 tools/acp-smoke.py

真跑一轮 ACP 握手(initializesession/newsession/prompt),期望看到:

initialize: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,...}}
session/new: {..."sessionId":"..."}
session/prompt: {..."stopReason":"end_turn","usage":{...}}
AGENT TEXT: ACP RELAY OK
STDERR: claude-acp-mira: → http://127.0.0.1:61855

卸载

rm ~/.local/bin/{mirasim-relay-port,claude-mira,claude-acp-mira}
rm -rf ~/.local/share/mirasim-acp

再删掉 ~/.zshrc 里的 alias 和 Zed settings.json 里的 claude-mira 条目。


限制

  • Mirasim 里必须有一个正在跑的 agent 会话,不只是应用开着。反代跟着 agent run 走,run 结束
    dispose()。没有代理时脚本会明确报错退出(不静默失败),并提示怎么让它起来。
  • MIRA_WAIT_SECS=<n> 可以让脚本等代理出现(轮询,默认 0 = 立刻失败)。适合「先启动 Zed
    agent,再去 Mirasim 开会话」这种顺序。
  • 端口在启动时发现一次。Zed 的 ACP server 是长驻进程,如果它绑定的那个会话结束了,端口就没了,
    这个 agent 也就断了 —— 需要在 Zed 里重启 agent / 开新 thread 重新发现。
    (想彻底解决得自己起一个常驻转发器,每个请求重新解析当前端口。本仓库没做。)
  • 走这条链路的请求计入你的 Mirasim 配额,并会被 relay 按应用自身的记录设置记录下来。
  • 适配器版本写在 install.shACP_VERSION 里,跟 Zed registry 的版本不会自动同步。

免责

这是互操作性工具:用你自己的账号、自己的额度,把你已经装在自己机器上的应用的本地端点接给
同机的其他客户端用。没有绕过任何计费或鉴权 —— 凭证始终由应用持有和刷新,脚本从不读取、复制或
存储任何凭证。

依赖的是 Mirasim 的内部实现细节(端口分配方式、本地端点行为),版本更新可能随时打破。
跟 Mirofish 和 Zed Industries 均无关联。

License

MIT

Reviews (0)

No results found