magpie 项目推荐:一个 7MB 的二进制,把 30 多种编码 Agent 接到同一份模型供给上

magpie(yetone/magpie)是 yetone 写的一个本地模型路由中枢。README 的开场白只有一句:“One place to pick every agent’s model: Codex on DeepSeek, Claude Code on Kimi, Gemini CLI on GLM, from the menu bar.”

这句话背后是一个所有重度编码 Agent 用户都踩过的坑:你机器上同时装着 Claude Code、Codex、Gemini CLI、OpenCode、Goose、Crush、Pi、Cline、Droid、Qoder、Zed……而它们的模型配置分散在 ~/.claude/settings.json、~/.codex/config.toml、~/.config/opencode/opencode.jsonc、~/.config/goose/config.yaml 各处,格式、字段、语义都不一样。 每换一家模型(今天想用 DeepSeek 的便宜额度,明天想用 Kimi 的长上下文),你要逐个文件去改。

magpie 的做法是:先做一层网关,再做一层配置编译器。 网关对外同时讲 OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、Gemini 四套协议;配置编译器则把"这个 Agent 用哪个 provider 的哪个模型"这一件事,精确地写进该 Agent 自己的配置文件里——只改那一个键,其他注释、顺序、缩进原样保留。

关键数据概览

项目数据
仓库yetone/magpie
Stars / Forks3,761 / 224(2026-10-01 实时抓取)
语言构成Go(6.62 MB)+ JavaScript(1.82 MB)+ HTML/CSS(553 KB)
许可MIT
创建时间2026-09-23(本文写作时刚满一周)
最近推送2026-09-30 23:05(当天仍在连续提交)
代码体量1,269 个文件,860 个 .go,其中 474 个 _test.go(测试占比 55%)
版本发布独立发布仓库 yetone/magpie-releases,最新 v0.1.550,一晚连发数个版本
贡献者36 人 / 926 次提交(主作者 yetone 840 次,已有外部贡献者 15/9/5/5… 次)
二进制体积桌面版 < 15 MB(Wails + 系统 webview,不打包浏览器);纯终端版 7 MB
网关地址127.0.0.1:3425(MAGPIE_ADDR 可改)

有一个数字比 star 更值得看:860 个 Go 文件里有 474 个测试文件。在一个一周内涨到 3.7k star 的项目里出现这种测试密度,通常意味着作者把"改坏别人机器上的配置文件"这件事当成了正经的工程问题——后面会看到,这确实是它最硬的部分。

起源故事:把"模型供给"从 Agent 里抽出来

要理解 magpie 为什么长这样,得先看它解决的问题的层次。

过去一年,编码 Agent 的生态呈现出一种奇怪的形态:Agent 是百花齐放的,模型供给却是锁死的。

  • Claude Code 默认只能吃 Anthropic 的模型(或者靠 ANTHROPIC_BASE_URL 这类环境变量 hack);
  • Codex 默认只吃 OpenAI;
  • Gemini CLI 默认只吃 Google;
  • 而 DeepSeek、Kimi、GLM、Qwen、MiniMax 这些价格和上下文都很有竞争力的模型,想接进任何一个 Agent 都要手工拼配置。

于是社区里出现了一批"转换器":claude-code-router、各种 *-proxy、CCSwitch 之类的工具,本质都是在给单个 Agent 做协议适配。但这类工具共同的短板是:如果你同时用五个 Agent,你就要维护五份互相不知道对方存在的配置,而且它们都只解决"转发",不解决"配置正确写回"。

magpie 的定位比这些工具高一层,它有意识地把问题拆成两个独立的子系统:

  1. 供给层:一个本地 HTTP 网关,把 N 个 provider(API key、中继、以及你已经登录的订阅)收敛成一个统一的模型目录,模型名一律写成 provider/model;
  2. 消费层:一个"配置编译器",知道 30 多种 Agent 各自的配置文件在哪、格式是什么、模型字段叫什么,负责把选择写回去。

这个拆法带来一个很舒服的性质:模型是"装一次、处处可用"的。 你加上一个 DeepSeek 的 key,它的模型立刻出现在 Claude Code、Codex、OpenCode、Goose……每一个 Agent 的选择器里,因为对它们来说那只是 http://127.0.0.1:3425/v1 上的一个普通模型。

技术亮点:把"兼容层"当正经工程做的三处细节

亮点一:网关的"四协议 IR"——先把请求压平,再重新展开

magpie 的网关要同时接四种客户端协议(OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、Gemini),上游又可能是其中任何一种。最容易写崩的做法是"两两互转"(4×4 = 16 条路径);magpie 选了教科书式的正确做法:定义一个公共中间表示(IR),所有请求先解析成 IR,再由 IR 编码成上游协议。

internal/gateway/ir.go 里的 IR 短得可以一眼看完:

type Part struct {
    Kind Kind          // text | image | file | tool_call | tool_result | thinking | web_search
    Text string        // text / thinking / 工具结果
    MediaType string   // image 或 file
    Data, URL string   // base64 或链接
    ID, Name string    // tool_call
    Args json.RawMessage
    CallID string; IsError bool   // tool_result
    Signature string              // thinking 签名
    Hits []Hit                     // web_search 的命中页
}

type Request struct {
    Model, System string
    Messages []Message
    Tools []Tool
    Effort string      // low | medium | high | xhigh | max
    Thinking bool
    CacheKey string    // Codex 的 prompt_cache_key:靠它把会话路由到 prompt 缓存所在
    Metadata json.RawMessage  // Claude Code 的 user_id,某些只服务 Claude Code 的中继会因此拒请求
    Namespaced map[string]nsTool  // Responses 客户端的命名空间工具(Codex 的 collaboration.spawn_agent)
    // ……
}

真正的功夫在注释里:IR 字段不是"四个协议的并集",而是"那些翻译时会被丢掉的东西"的清单。 举两个例子:

  • CacheKey 存在的理由是:OpenAI(以及它前面的中继)用 prompt_cache_key 决定把请求路由到哪台有缓存的机器上。Codex 发的是它 thread 的 id,magpie 必须原样带过去,否则每一次请求都可能落到没有缓存的实例上,成本直接翻倍。
  • Metadata 存在的理由是:某些只服务 Claude Code 的中继,会把"没有 metadata.user_id 的请求"直接拒掉(代码注释里标了 issue #359)。这不是协议问题,是现实世界的兼容问题,而 IR 里的一个字段就把它兜住了。

流式也一样,被压成 11 种事件:

const (
    KStart      // 消息开始:MsgID、Model、input 侧 usage
    KText       // 文本增量
    KThink      // 推理增量
    KSig        // 思考签名
    KToolStart  // 工具调用开始:ID、Name
    KToolArgs   // 工具参数的部分 JSON
    KStop; KUsage; KError; KSearch
)

编码器把同一串事件分别渲染成 SSE、Anthropic 的 message_* 事件流或 Gemini 的 generateContent 分片。流式、tool call、reasoning 三者同时要保真,这是"转换层"里最容易出事的地方,用一个 IR + 一个事件流来承载,是能长期维护的唯一走法。

亮点二:Claude 订阅不是"重放 token",而是驱动真的 claude 进程 + MCP 工具桥

这是 magpie 里最"不按常理出牌"、也最有料的一处。

大多数工具把"Claude 订阅"当成一个 OAuth token 来转发。magpie 明确拒绝这么做,internal/gateway/claude_subscription.go 开头的注释写得非常直白:

Claude Subscription is driven through the genuine Claude Code binary rather than by replaying its OAuth token over HTTP. Anthropic applies subscription eligibility checks to request content (not just credentials/CCH); prompts generated by Pi, OpenCode and other harnesses can otherwise be routed to Extra Usage.

翻译一下:Anthropic 不只是校验凭证,它会看请求内容——用别的 harness 生成的 system prompt 发过去,可能被判定为"第三方流量",于是你的订阅额度被绕过,走起了按量计费的 Extra Usage。magpie 的对策是把"一致性"做到极致:真的去启动本机的 claude 二进制,让这一轮请求以 Claude Code 的真实身份发生。

那工具调用怎么办?调用方(比如 OpenCode)提供的工具描述并没有出现在 Claude Code 里。magpie 的答案是一个 stdio MCP 桥(internal/claudebridge/mcp.go,整个文件只有 4.6 KB,注释写着"intentionally implements only the MCP methods Claude Code needs"):

// 隐藏的 stdio MCP 子进程:由真 claude 二进制启动,工具执行交回父进程 magpie
switch req.Method {
case "initialize":   // 声明 tools 能力,协议版本 2025-06-18
case "tools/list":   // 把调用方的工具原样报给 Claude Code
case "tools/call":   // ⚠️ 这里刻意阻塞:一直等到 magpie 把结果拿回来才返回
    // Claude Code 会在 _meta 里带上 claudecode/toolUseId,用它当 id 回传
    // 没有的(比如 Cursor)由 helper 自己生成 call_xxxxxxxxxxxx
    http.Post(callbackURL, {ToolCallID, Name, Arguments})
}

于是每一轮的生命周期是这样的:

调用方 ──HTTP(tools: [...])──▶ magpie 网关
                                │ 启动/复用 真 claude 进程,工具以 MCP server 注入
                                │
            claude 决定调用工具 ──MCP tools/call──▶ helper(阻塞中…)
                                │
   magpie 把 tool_use 回给调用方 ◀──┘   (调用方自己执行工具,它本来就有这些工具)
                                │
调用方下一次 HTTP 请求带上 tool_result ──▶ magpie 交回同一个 claude 进程,解除阻塞

这套"停泊(park)/ 解除"的机制有好几处必须处理的现实问题,代码里都留了痕迹:

  • 进程不能一轮一杀:一轮结束后 claude 进程会保持存活、stdin 不关,留给这个会话的下一轮。注释解释了原因——下一轮只告诉它"用户刚说了什么",这样 Claude Code 自己维护的会话和它已经缓存的 prompt 就还能复用;如果每轮都重新起进程、把全部历史塞成一条消息,缓存全废。
  • 闲置是有代价的:进程保留 idleLongest = 20min、最多 idleMost = 6 个,最早等待的先放掉——因为每一个闲置的 run 都是一个真实的 Claude Code 进程。
  • 一次回复里的两个工具调用是串行发出的:Claude Code 要等第一个工具结果回来才会发第二个调用,而调用方可能已经把两个都跑完、两个结果一起发回来了。于是有了 early map[string]mcpToolResult 来暂存"提前到达的结果"。
  • MCP 客户端一分钟就超时:所以有个 patience 机制,工具跑太久就先告诉 Agent"还在跑",结果稍后由 magpie_wait 收回(late)。
  • Cursor 的流不带完整工具调用:所以有个 onCall 回调,由 MCP helper 每次交接时通知网关,网关自己补一个 message start。

顺带一句,README 也如实标注了这套设计参考了 pi-claude-bridge(MIT)的停泊式做法。把"合规性约束"翻译成"进程与协议层面的工程",而不是绕过去——这是我在这批候选里最认可的一处取舍。

亮点三:配置文件定点编辑——注释、顺序、缩进一个都不能丢

第二个子系统看起来平淡,但它是这个项目的"信任基础":一个会重写你 ~/.claude/settings.json 的工具,只要弄丢一次注释,用户就再也不会用它。

magpie 的做法是手写一套无依赖的定点编辑器(internal/edit/,按格式分成 json.go / toml.go / yaml.go / text.go),核心原则是"只替换目标路径所在的字节范围"。

JSON 这一路尤其讲究,用 tidwall/jsonc + gjson 做了一次"等长去注释"的技巧:

func setJSONBytes(raw []byte, keyPath string, value any) ([]byte, error) {
    // 在等长副本里把注释"挖空",这样 gjson 拿到的 offset 能 1:1 映射回原始字节
    stripped := jsonc.ToJSONInPlace(append([]byte(nil), raw...))
    root := gjson.ParseBytes(stripped)
    // …沿 keyPath 下钻,把值写成原样字节,其余原封不动
}

“等长副本"是关键:注释被替换成等量空格,而不是删掉。 这样解析器的位置信息对原文件依然成立,于是可以在保注释、保顺序、保缩进的前提下精确改一个键。最后统一走 WriteAtomic 原子落盘——这一点对"Agent 正在跑的机器"尤其重要,没人希望配置文件被写坏一半。

TOML 走的是另一条更"保守"的路线:把整个表当单位。 注释写着"magpie owns the tables it writes”,比如它自己写的 model provider 表会被整块替换,而这个文件里其他每一行都不动:

// TOMLTables 列出文件里显式的普通表头(不含 array table),名字是表头的 key path
// SetTOMLTable 用给定键替换 `name` 表,或追加;同名 array table 会被拒绝
func SetTOMLTable(path, name string, kvs ...KV) error

还有一个细节把"精确"落到了实处:因为 Agent 只在启动时读配置,所以改模型必须正确地提示用户重启(Codex 尤其明显——magpie 会写一个 ~/.codex/magpie-models.json 并用 model_catalog_json 指过去,让模型出现在 Codex 自己的 /model 列表里,但列表是启动时读的)。同理,magpie 也诚实说明:写窗口大小这类值会同步进各 Agent 自己的文件,已经在跑的会话仍会用启动时那个窗口。

附赠:路由、密钥轮换与"订阅互用"

这三件事虽是配角,但很能说明它面向的是真实使用场景:

  • Routing groups:把多个模型(同一 provider 或跨 provider)当成一个 group/<id> 给 Agent 用。路由策略是 smart(默认:在还有额度的订阅里,挑最快续期的先用)/ order / rotate / usage(最少使用优先)。
  • 密钥轮换与"休息":internal/gateway/routing.go 里,某个 key 失败后不是简单拉黑,而是按失败原因决定休息多久——欠费休息半小时,配额耗尽休息到它自己声明的重置时间,限流休息到对方说的重试时间,其余默认一分钟并逐次加长。选择 usage 策略时按 token 量加权,且用了指数半衰期(usageHalfLife = time.Hour,一小时减半)——这是"把最近使用量当作负载信号"的标准做法。
  • 订阅互用:你登录过的 Claude Code、Codex(ChatGPT)、Copilot、Devin、Qoder、Grok、Cursor 的登录态,都会被识别成一个 provider,模型在其他 Agent 里直接写作 claude/claude-sonnet-5、codex/gpt-5.5、copilot/claude-sonnet-4.5。magpie 每次读取的是 Agent 自己的凭证,并按 Agent 的方式刷新 token——它自己不存订阅凭证(你登出,provider 就消失)。
  • 成本账本:调用按"有效价格"记账,查找顺序是 本模型的自定义价格 → <provider id>/* → provider 自己的目录 → models.dev。README 坦率地说了两个边界:账本是读取时重算的(所以它叫估算而不是账单),而且用量记录不保留"哪个 key/账号服务了这次调用",因此一个 provider 对不同账号收不同价格的场景无法精确核算。

优点与要注意的地方

优点:

  • 一次配置,全体受益:网关把模型供给收敛成 provider/model 一个命名空间,新模型发布当天刷新目录就能被所有 Agent 用上;
  • 写配置的手艺罕见地好:定点编辑保注释/保顺序/保缩进 + 原子落盘,还带一个"值被替换前的原件"暂存(stash.json),切回原生模型时能还原;
  • 对现实约束诚实:Claude 订阅走真实 claude 进程 + MCP 桥、Codex 的 ChatGPT 后端只支持流式就读流式翻译、Gemini CLI 的登录态变化如实写进文档(Google 已不再向个人账号提供 Gemini CLI 登录,需要 Gemini Code Assist 的 Standard/Enterprise 计划并指定 Cloud 项目);
  • 四套协议 + 五套客户端 API 全覆盖:/v1/chat/completions、/v1/responses、/v1/messages(含 count_tokens)、/v1beta/models/{model}:generateContent,任何能改 base URL 的工具都能接;
  • 形态齐全:一个二进制同时给你菜单栏 App(Wails)、magpie tui、纯 CLI、magpie web 浏览器界面,还有 distroless 的 Docker 镜像;
  • 可运维:备份用 AES-256-GCM + PBKDF2-SHA256 在本地加密,可用 WebDAV / S3(AWS、R2、B2、MinIO、NAS)同步多机,且用条件写(conditional write)避免覆盖别的机器的更新。

要注意的地方:

  • 它要改你的 Agent 配置文件:这是它的能力也是它的风险面。虽然原子写 + 保注释 + 暂存还原已经很克制,但第一次用之前建议先 magpie ls 看清当前状态,并且知道 magpie backup 怎么用;
  • Claude 订阅路径有硬前提:必须装了正版 Claude Code 并已登录,而且这条路径是"以子进程方式驱动 claude",会带上启动开销与闲置进程管理;
  • Docker 形态的默认端口要留神:文档明确说未开启网络共享前,网关接受任何 key(包括 Bearer magpie),所以官方 Docker 示例把端口发布在 127.0.0.1 上——别直接改成 -p 3425:3425,开了 LAN 共享后请求还必须带 lanKey;
  • 远程分享有边界:Windows 与 Linux 构建尚未签名,而且如果机器上有系统代理,本地客户端(Codex 等)可能把 127.0.0.1 的请求也走代理,需要放行;
  • 统计默认是开的:每天向 PostHog 发一条匿名事件(随机 install-id + 版本 + 系统架构,不含 key/模型/提示词/用量),可用设置里的 “Count me as a user” 或 DO_NOT_TRACK=1 / MAGPIE_NO_STATS=1 关闭;从源码构建则不发;
  • “订阅共享"的合规性由使用者自担:把 Claude/Copilot 的订阅登录态开放给其他 Agent 使用,可能与各家服务条款存在摩擦(README 对 Google 账号也提示了"可能被暂停”)。

适合谁

  • 同时用两个以上编码 Agent 的人:手上既有 Claude Code 又有 Codex、Gemini CLI、OpenCode,受够了四处改配置;
  • 想让贵订阅"摊薄"到更多工具的人:把 Claude/Copilot/ChatGPT 的订阅登录态变成所有 Agent 都能用的 provider;
  • 在做国产/廉价模型切换的人:DeepSeek、Kimi、GLM、Qwen、MiniMax、SiliconFlow、火山方舟等一大批 provider 都是"选个预设、粘个 key";
  • 手上有多个 key/中继、需要轮换与配额可见性的重度用户:Routing group + 休息策略 + 成本账本正好覆盖;
  • 想学"兼容层怎么写"的 Go 工程师:四协议 IR、事件流渲染、保注释的定点编辑、隐藏 MCP 桥,四个子系统都是可读性很高的样本(860 个 Go 文件、474 个测试)。

一句话总结:magpie 表面上是个"菜单栏里换模型"的小工具,实际做的是把编码 Agent 生态里最脏的那层——模型供给与配置文件——收敛成一个可被程序正确写入的接口。它对 Anthropic 订阅限制的应对(改用真实 claude 进程 + MCP 工具桥)和对 JSON/TOML 的定点编辑,都是那种"不解决就做不成、解决了也没人夸"的活,而它选择正面做掉。

项目地址:https://github.com/yetone/magpie 官网 / 安装:https://usemagpie.ai(curl -fsSL https://usemagpie.ai/install.sh | sh) 发布记录:https://github.com/yetone/magpie-releases/releases