模型、提供方与提示词路由

小米平台的加密回调登录与免费通道退场,xAI 与 Codex 登录,模态与附件的新规则,按模型家族挑选系统提示词,以及模型层源码的其他改动。

作者 David更新于 第 29 篇(共 33 篇)

MiMo Code 的模型层沿用 OpenCode 的骨架:models.dev 目录加上环境变量、凭据、插件与配置,层层叠成 Provider 状态;transform.ts 抹平各家在消息与参数上的差异;登录走插件的 auth 钩子。这些机制见提供方目录、提供方适配层与认证与提供方插件,那几篇对照的是 OpenCode v1.18.30,与 MiMo 的 v1.14.19 底座在细节上有出入。本篇只看 MiMo 往里加了什么:自家平台的登录与免费通道、xAI 与 Codex 两个登录插件、模型能力相关的新规则,以及系统提示词怎样按模型家族挑选。

怎么用

场景做法
TUI 登录/login 与 /connect 打开同一个对话框:小米(推荐)、Codex、从 Claude Code 导入,其后才是其余提供方
命令行登录mimo providers login(别名 mimo auth login)先让你在 MiMo 与其他提供方之间选择,-p xiaomi 直接进入小米登录;mimo auth whoami 显示已登录的 uid
登出/logout 只删除 xiaomi 的凭据
声明模态/modalities 勾选当前模型支持的输入(image、audio、video、pdf),写入全局配置
模型分组model_groups 定义分组,agent、actor、workflow 里写模型的地方可以用组名代替 provider/model;vision_model 指定看图任务用的模型
提供方与模型选项提供方级 only_configured_models、headerTimeout、chunkTimeout;模型级 voice_design、voice_clone、cachePromptTTL
环境变量MIMOCODE_MIMO_ONLY、MIMOCODE_DISABLE_PROVIDER_ENV、MIMOCODE_CODEX_MODE、MIMOCODE_ENABLE_DYNAMIC_SYSTEM_PROMPT、MIMOCODE_MAX_PROMPT_IMAGES、MIMOCODE_MAX_PROMPT_IMAGE_SIZE、MIMO_PLATFORM_URL

两个斜杠命令登记在 packages/opencode/src/cli/cmd/tui/app.tsx:826 与 app.tsx:838,登出在 app.tsx:849;命令行入口在 packages/opencode/src/cli/cmd/providers.ts:533。

小米平台:加密回调换来一把 API key

内置插件表的头两项是 MiMo 新加的 MimoAuthPlugin 与 AnthropicProxyPlugin,其后是上游原有的 Codex、Copilot 等,中间插入 MiMo 自己的 XaiAuthPlugin(packages/opencode/src/plugin/index.ts:142)。

MimoAuthPlugin(packages/opencode/src/plugin/mimo.ts:86)有三个钩子:config 把 xiaomi 注册为配置提供方,没登录也能出现在列表里,名称与 API 地址留给模型目录;auth.loader 把登录时拿到的 base_url 设成该提供方的 baseURL(mimo.ts:102);chat.headers 给发往 xiaomi 的请求加上 X-Mimo-Source 头,值为 mimocode-cli(mimo.ts:198)。

唯一的登录方法叫“浏览器登录”,类型标为 oauth,但拿回来的不是 access token,而是一把普通的 API key:

  1. 生成一对 X25519 密钥(mimo.ts:26),在本机随机端口起一个 HTTP 服务(mimo.ts:114)。
  2. 拼出 https://platform.xiaomimimo.com/authorize 的地址(域名可用 MIMO_PLATFORM_URL 覆盖),参数带上公钥 pk、回调地址与 key_name(mimo.ts:76)。key_name 形如 mimo-code-cli-key- 加 8 位十六进制,首次生成后存进数据目录的 mimo-key-name 文件复用(mimo.ts:15)。自动打开的地址回调本机端口;交给界面展示的是另一个版本,回调平台自己的 /authorize/code/callback 页面,浏览器不在本机时由用户复制授权码粘回来。
  3. 平台把结果加密后放进回调参数 u,本机服务解密成功就把浏览器重定向到平台的成功页,最多等 5 分钟(mimo.ts:130)。

密文格式写在注释里(mimo.ts:36):

const encrypted = Buffer.from(encryptedBase64, "base64url")
// Format: ephemeralPublicKey(32 bytes) + nonce(12 bytes) + ciphertext + tag(16 bytes)
const ephemeralPub = encrypted.subarray(0, 32)
const nonce = encrypted.subarray(32, 44)
const ciphertextAndTag = encrypted.subarray(44)
const tag = ciphertextAndTag.subarray(ciphertextAndTag.length - 16)
const ciphertext = ciphertextAndTag.subarray(0, ciphertextAndTag.length - 16)

随后用本机私钥与平台的临时公钥做 ECDH,对共享密钥取 SHA-256 作为 AES-256-GCM 的密钥解密(mimo.ts:51)。明文是 sk、uid、url 三个字段:sk 存为 type: "api" 的密钥,uid 与 url 进 metadata(mimo.ts:175)。粘贴的授权码走同一个解密函数。

两个前端各包了一层。TUI 的 MimoOAuthFlow(packages/opencode/src/cli/cmd/tui/component/dialog-mimo-login.tsx:171)挂载时就开始等浏览器回调,输入框同时接受粘贴,成功后若目录里有 mimo-v2.5-pro 就把它设为当前模型(dialog-mimo-login.tsx:196)。命令行的 mimoLogin(providers.ts:262)让浏览器回调与标准输入赛跑,粘贴的码解不开可以重试,共 3 次(providers.ts:285)。开源版还留了一个 src/ext 登录扩展点(providers.ts:242),但仓库里没有这个目录。

同文件的 AnthropicProxyPlugin(mimo.ts:205)只在 anthropic 既有已保存的凭据、又配置了 baseURL 时生效:删掉请求头里的 anthropic-beta,并在 SSE 流里读到 message_stop 事件后主动关闭流,不再等上游断开连接。“从 Claude Code 导入”会写入这个 baseURL,见从 Claude Code、Codex 与 OpenCode 迁移。

免费通道 mimo-auto 的退场

MiMo 曾提供免登录的 mimo/mimo-auto 通道。开源代码里找不到 mimo 提供方本身的定义,只有几处按 ID 的特判:干净安装时 TUI 优先把它选为默认模型(packages/opencode/src/cli/cmd/tui/context/local.tsx:216);配置里声明了 mimo-auto 时强制把它标为支持图片输入(packages/opencode/src/provider/provider.ts:1424);首次使用前要先在 DialogAgreement 里同意用户协议(packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:1130);遇到限流排队而重试时,最多每 24 小时弹一次 DialogTokenPlan 推荐订阅 Token Plan(packages/opencode/src/cli/cmd/tui/routes/session/index.tsx:461)。

退场时间写死在 packages/opencode/src/util/free-api-sunset.ts:1:

export const FREE_API_SUNSET_AT = Date.parse("2026-07-26T10:00:00.000Z")

换算成北京时间是 7 月 26 日 18:00,早于 v0.1.14 的发布日期,所以这个版本里下面三层都已生效:

  • 服务端:getLanguage 与 getSpeech 遇到 mimo/mimo-auto 直接抛错(provider.ts:1805、provider.ts:1866)。
  • TUI 提交:shouldBlockFreeApiRequest 拦下发给模型的输入并弹窗,提示用 /login 登录或配置第三方 API;只在本地执行的斜杠命令与 shell 模式放行(prompt/index.tsx:1113)。
  • 显示:模型名去掉“限免至 7 月 26 日”的说明。createFreeApiSunsetSignal 启动时按剩余毫秒数设一个定时器,TUI 开着跨过这个时间点也会即时切换(packages/opencode/src/cli/cmd/tui/util/free-api-sunset.ts:19)。

服务端抛出的错误原文是:

MiMo free API service has ended. Sign in or configure a third-party API.

与此同时,上游 opencode 提供方(OpenCode Zen)的免费模型被一律隐藏:未认证时整个提供方为空,认证后也只剩付费模型(provider.ts:309)。上游 v1.14.19 的逻辑正相反,没有密钥时删掉收费模型,用 apiKey: "public" 开放免费模型。

xAI 与 Codex 登录

v1.14.19 没有 xAI 登录插件,packages/opencode/src/plugin/xai.ts 是 MiMo 加的,提供三种方式(xai.ts:594):

方式做法
浏览器PKCE 授权码流程,复用 Grok CLI 公开的 client_id,回调固定在 127.0.0.1:56121;注释说明 xAI 按注册的地址校验 redirect_uri
无头RFC 8628 设备码:authorization_pending 时按间隔加 3 秒余量再查,slow_down 时间隔加 5 秒,截止时间取服务端的 expires_in,缺省 5 分钟
API key手动输入

loader 返回占位 key 与自定义 fetch:存储的过期时间或 JWT 里的 exp 距现在不足 120 秒就刷新(xai.ts:534),并发请求共用同一次刷新,新 token 写回 auth.json,写失败也不影响本次请求。

Codex(ChatGPT 订阅)沿用上游的 packages/opencode/src/plugin/codex.ts,改动很少:上游在 OAuth 登录后只保留名字含 codex 或列在 8 个型号白名单里的模型,MiMo 删掉了这段过滤,openai 目录里的模型全部保留,价格一律清零(codex.ts:372);刷新 token 时传入请求的 AbortSignal;错误页对错误信息做 HTML 转义;两个方法改名为 Codex via ChatGPT Pro/Plus (browser) 与 Codex via ChatGPT Pro/Plus (headless)。TUI 登录框里的 Codex 条目直接调用 openai 提供方的第 0 个方法(dialog-mimo-login.tsx:48)。

窗口、模态与附件

窗口与输出上限。在配置里声明、目录里又查不到的模型,limit.context 缺省为 DEFAULT_CONTEXT_WINDOW,即 1000000,并记一条警告提示去配置(provider.ts:1391),上游缺省为 0。输出上限看 usesLargeModelDefaults:提供方是 mimo 或 xiaomi,或模型 ID 含 claude、gpt、mimo 的,缺省 128000(packages/opencode/src/provider/transform.ts:28)。maxOutputTokens 对这类模型直接返回 128000,不再与模型声明的 limit.output 取小(transform.ts:1850);其余模型仍是上游的“目录值与 32000 取小”。

模态。/modalities(packages/opencode/src/cli/cmd/tui/component/dialog-modalities.tsx:66)把勾选结果写进全局配置里该模型的 modalities.input,输出模态照抄现有声明,然后重建实例。需要看图时由 getVisionModel 选模型:配置了 vision_model 且可用就用它,否则在所有支持图片输入的模型里先挑 mimo、xiaomi 自家的,再按输入单价从低到高(provider.ts:1965、provider.ts:1122)。

packages/opencode/src/provider/capability-registry.ts 名字像一张模型能力总表,其实只服务 MCP 的 sampling/createMessage。服务器发来的采样请求可能带图片或音频,它把“模型声明的输入模态”与“SDK 适配包能否序列化这种媒体”两道闸门取交,适配包一侧分 supported、unsupported、unknown 三态,未知一律不选,单个媒体上限 20 MiB(capability-registry.ts:54)。调用方在 packages/opencode/src/mcp/sampling.ts:624,见 MCP:采样与工具检索。

图片体积。transform.ts 在发送前新增了 limitImages:只有 Anthropic、Bedrock,以及经网关、OpenRouter、Copilot 转发的 Claude 模型才套用上限(transform.ts:794),单图解码后不超过 4500000 字节、最长边不超过 2000 像素(packages/opencode/src/provider/image.ts:7)。超限的 PNG、JPEG 逐级缩小、降低质量,重新编码成 JPEG;仍然超限或格式无法解码,就换成一段占位文本。历史消息里的图片同样处理,一张超大截图不会让之后的每次请求都失败。MIMOCODE_MAX_PROMPT_IMAGES 限制每次请求带的图片张数(丢弃最旧的),MIMOCODE_MAX_PROMPT_IMAGE_SIZE 覆盖字节上限。

工具结果里的附件。上游用一个布尔值判断 SDK 是否支持在工具结果里放媒体,不支持就把全部媒体挪进一条补充的 user 消息。MiMo 改为逐个附件决定去向(packages/opencode/src/session/tool-attachment.ts:80):

export function routeToolAttachment(input: {
  model: Provider.Model
  attachment: ToolAttachment
  allowNative: boolean
}): ToolAttachmentRoute {
  if (!modelAcceptsMime(input.model, input.attachment.mime)) return "placeholder"
  if (input.allowNative && providerAcceptsNative(input.model, input.attachment)) return "native"
  if (providerAcceptsSynthetic(input.model, input.attachment)) return "synthetic"
  return "placeholder"
}
  • native:附件留在工具结果里。只有 Anthropic、Bedrock(图片与 PDF)和 Gemini 3(另加音频、视频)走这条路,而且必须是内联的 data URL(tool-attachment.ts:64)。
  • synthetic:附件搬进紧随其后的一条 user 消息,开头是常量 SYNTHETIC_ATTACHMENT_PROMPT,再逐个注明来自哪次工具调用(packages/opencode/src/session/message-v2.ts:1065)。音频只对 @ai-sdk/openai-compatible(wav、mp3、mpeg)与 Google 系开放,视频只对 Google 系开放(tool-attachment.ts:43)。
  • placeholder:模型不接受这种 MIME,或两条路都走不通,就换成一句说明文字,告诉模型附件已保留、但无法发给当前模型或提供方(tool-attachment.ts:142)。

失败的工具调用不走 native(message-v2.ts:1017)。

系统提示词按模型家族挑选

主提示词由 SystemPrompt.provider 决定(packages/opencode/src/session/system.ts:35):

export function provider(model: Provider.Model, harness?: HarnessMode) {
  if (usesGPTToolset(model.id, harness, model.api.id, model.family)) return [PROMPT_GPT]
  const prompt = (id: string) => {
    // An explicit process override can route GPT here; keep its prompt aligned
    // with the default tool schema instead of advertising Codex-only tools.
    if (isGPTModel(id)) return PROMPT_DEFAULT
    if (id.includes("gpt-4") || id.includes("o1") || id.includes("o3")) return PROMPT_BEAST
    if (id.includes("gemini-")) return PROMPT_GEMINI
    if (id.includes("claude")) return PROMPT_ANTHROPIC
    if (id.toLowerCase().includes("trinity")) return PROMPT_TRINITY
    if (id.toLowerCase().includes("kimi")) return PROMPT_KIMI
    if (id.toLowerCase().includes("deepseek")) return PROMPT_DEEPSEEK
    if (id.toLowerCase().includes("glm")) return PROMPT_GLM
    if (id.toLowerCase().includes("minimax")) return PROMPT_MINIMAX
  }
  return [prompt(model.id) ?? prompt(model.api.id) ?? PROMPT_DEFAULT]
}

与上游 v1.14.19 相比有三处不同:

  1. 先判 Codex 模式。usesGPTToolset(packages/opencode/src/tool/gpt.ts:41)对 ID 含 gpt 的模型(gpt-oss 除外)返回真;MIMOCODE_CODEX_MODE=true 或会话首条消息带 harness: "codex" 能把其他模型也拉进来,MIMOCODE_CODEX_MODE=false 则全部关掉(gpt.ts:11)。命中后用 gpt.txt,并换用 GPT 专属工具集(packages/opencode/src/session/prompt.ts:1509);这份 gpt.txt 在 MiMo 里重写成了自称 Codex 的提示词,上游单独的 codex.txt 被删掉。Codex 模式被强制关闭时 GPT 模型落到 default.txt,因此 beast.txt 只剩 ID 含 o1、o3 而不含 gpt 的模型会用到。
  2. 先看 model.id 再看 model.api.id。上游只看 api.id。
  3. 新增三个家族:deepseek、glm、minimax。
文件行数与上游的关系
default.txt170重写(上游 105 行),加入 agent 模式、权限、任务与子智能体、技能、记忆等 MiMo 概念
gpt.txt226重写为 Codex 风格(上游 107 行)
anthropic.txt39重写为精简版(上游 105 行)
deepseek.txt、minimax.txt、glm.txt131、140、51新增
beast.txt155自称改为 MiMoCode,末尾加一段自主运行的安全边界
kimi.txt、gemini.txt、trinity.txt95、155、97基本只改自称,另有几句随工具名与路径规则调整

新增的三份有些值得留意的痕迹:deepseek.txt 与 minimax.txt 的工作流段落都要求在 packages/opencode 目录下跑 bun typecheck,deepseek.txt 的编码规范一节还列出 Drizzle schema 字段用 snake_case 这类规则(packages/opencode/src/session/prompt/deepseek.txt:86),与仓库根目录 AGENTS.md 的风格指南同源,这些规则会随系统提示词发给用户的任何项目;minimax.txt 的第 126、127 行是重复的同一句;glm.txt 写到工具调用约定就结束了,没有任务与子智能体两节。另有一份 default.old.txt,代码里没有引用。

环境信息默认不发。SystemPrompt.environment 开头就检查 MIMOCODE_ENABLE_DYNAMIC_SYSTEM_PROMPT,未设置时返回空数组(system.ts:79),调用方同样受这个开关控制(prompt.ts:4444)。打开后分两种写法:命中 anthropic.txt 的模型拿到一段带当前分支、主分支、git status 与最近两次提交的环境说明,按会话创建时间缓存(system.ts:81);其余模型拿到以 MiMo Code Agent 自称开头的 <env> 块,模型不支持图片时再附一段 <vision-capability>,列出最多 3 个可以交给子智能体看图的模型(system.ts:139)。两种写法都附带一句要求回复语言跟随用户的指令。

provider.ts 与 transform.ts 的其他改动

改动出处
openai 提供方默认等响应头 5 分钟;所有提供方的 SSE 分块间隔缺省 8 分钟(上游不设),注释说是为 mimo-v2.5-pro 在 MiMo Router 上重建上下文后的冷启动首字延迟留余量provider.ts:45
xiaomi 的模型 ID 同时含 mimo 与 ptc 两段时改走 Responses API;xiaomi 走 @ai-sdk/openai-compatible 时换用内置的 copilot SDK,把 exec 工具声明成 Responses 的自由格式工具,见 exec 与 QuickJS 沙箱provider.ts:334、provider.ts:1734
getSmallModel 在未配置 small_model 时解析 lite 分组;ultra、standard、lite 三个内置层级未配置时回落到默认模型,所以什么都不配时标题生成这类轻量调用就用默认模型,不再像上游那样按 haiku、flash、nano 自动挑;其他未知组名报错并给出相近的组名provider.ts:1951、provider.ts:1910
defaultModel 遇到配置里失效的 model 不再直接返回,最后兜底时只挑支持工具调用、有文本输入、窗口非零的模型provider.ts:1992
提供方设 only_configured_models 后,models 充当隐式白名单provider.ts:1573
MIMOCODE_DISABLE_PROVIDER_ENV(MIMOCODE_MIMO_ONLY 会连带打开)时不再凭环境变量点亮提供方provider.ts:1440
配置里新声明、走 @ai-sdk/openai-compatible 的 deepseek 模型默认把推理放进 reasoning_content 回传provider.ts:1377
新增 getSpeech 与按模态推断的 modelKind,供内置 LLM 服务的语音接口使用provider.ts:1865、provider.ts:2099
发送前先补齐空内容,再保证请求以 user 消息结尾(必要时追加一条内容为 Continue. 的 user 消息),两步的先后顺序写成了注释里的契约transform.ts:995
缓存断点改为“最后一条 system 加最后两条消息”,工具表的最后一项也打断点;只有确认支持内联标记的提供方才打transform.ts:554、transform.ts:266
@ai-sdk/openai、@ai-sdk/azure 的工具显式设 strict: false;所有提供方的工具 schema 先把根部的 anyOf 拍平成单个 objecttransform.ts:1107、transform.ts:2000
MiMo 网关把内容审核与风控拦截放在 HTTP 400 里,error.ts 把错误码 421、441 翻译成可读信息packages/opencode/src/provider/error.ts:170

请求前缀与缓存命中的整体安排见压缩、修剪与请求前缀。

下一篇:内置 LLM 服务——每个 MiMo 实例都挂着一套 OpenAI 兼容接口,技能和子进程凭临时令牌借用模型,而拿不到真实密钥。

本页目录