Provider 规则、模型目录与选项映射

ZCode 用一份随包分发、可远程更新的规则集描述账号型 Provider、第三方模板和每个模型的能力,再叠上个人 BYOK 配置与账号权益两层;“推理强度”“输出上限”两个统一选项则由一门受限的 CEL 小语言翻译成各家请求体字段。

作者 David更新于 23 篇(共 47 篇)

ZCode 不在代码里写死“有哪些模型、每个模型能做什么”。这些事实放在仓库根的 config/provider/zcode-builtin.json:一份 6212 行、约 18 万字节的规则集,随客户端打包,运行时还会从 ZCode 平台拉取新版本。它回答三个问题:有哪些 Provider(账号型套餐与第三方模板),每个模型有什么能力(上下文窗口、多模态、工具调用),以及“推理强度”“输出上限”这两个统一选项在不同协议下该写成哪些请求字段。

这一篇讲规则集本身、它怎样与个人配置和账号状态叠成一份 Registry、模型选择怎样解析与持久化,以及选项映射这门小语言。规则解析出的事实怎样变成一次模型请求,留给下一篇模型适配层;账号登录与各档套餐见账号、Coding Plan 与闲时计划

位置职责
config/provider/zcode-builtin.json内置规则集,代码里称为 ZCode Built-in
packages/provider纯领域层:zod schema、规则叠加、三层解析、Registry、模型选择(4827 行)
packages/provider-nodeNode 侧 IO:随包基线、缓存、远程同步、个人配置文件(1895 行)
packages/model-option-map选项映射小语言(867 行)
packages/shared/src/model-*.ts跨层共享的数据契约
scripts/builtin-provider-config.mjs构建期校验并暂存规则集

三层配置叠成一份 Registry

运行时的 Provider 事实来自三层:Built-in 规则集、个人配置文件 provider_config.json、账号服务给出的权益与连接状态。ProviderConfigResolver 把它们叠起来,只把完整、可执行的 Provider 与模型发布进 ProviderRegistrypackages/provider/src/resolver.ts:182)。

图表加载中…

几条叠加规则值得记住:

  • 覆盖语义统一:稀疏层里缺省的字段继承下层,null 表示清空,嵌套配置递归覆盖,其余值(包括数组)整体替换(packages/provider/src/config-overlay.ts:2630)。
  • 账号层只能改内置的账号型 Provider:不在 Built-in 里的 ID 会被丢掉(resolver.ts:184);它能写的只有权益 entitled 和 Start Plan 的模型清单(packages/provider/src/account-provider-resolution.ts:106)。反过来,个人配置不许给 account: 前缀的 Provider 声明访问方式(packages/provider/src/config/rule-data-schema.ts:116)。
  • 模板在最底:带 templateId 的 Provider 先铺模板配置,再叠自己的覆盖(resolver.ts:192201)。
  • 进 Registry 的门槛:Provider 配置要能通过完整 schema 校验,模型要 enabled,账号型 Provider 还要有权益且不是“非当前连接”(resolver.ts:249253);visibility: "hidden" 的 Provider 模型可执行、但不出现在选择器里(resolver.ts:280)。
  • 排序zai-familybigmodel-family 两组账号型 Provider 永远在最前,其后是其他内置与个人 Provider,个人可以用 providerOrder 调整后者(resolver.ts:355)。

规则集:一个文件,七类规则

文件头只有三个字段:schemaVersion 为 1,revision 为 30,其余都在 config 里(config/provider/zcode-builtin.json:23)。用脚本统计各组条数:

规则组条数匹配键作用
providerConfigRules.templateRules20templateId第三方模板:访问方式、API 类型与地址、内置模型清单、图标
providerConfigRules.providerRules8providerId固定的账号型 Provider,ID 都以 account: 开头
modelConfigRules.modelRules84modelMatch 正则按模型 ID 给能力与选项规格
modelConfigRules.modelApiRules72另加 apiTypeMatch同一模型在不同协议下的差异,主要是选项映射
modelConfigRules.providerSiteRules52另加 baseUrlMatch同一模型在某个站点上的差异
modelConfigRules.templateModelRules244templateIdmodelId模板里每个模型默认开不开
modelConfigRules.builtinProviderModelRules26providerIdmodelId账号型 Provider 的模型启用

五组模型规则解析时按固定顺序拼成一条链:modelmodel-apiprovider-sitetemplate-modelprovider-model,个人配置的精确规则排在最后(packages/provider/src/config/schema.ts:78packages/provider/src/config/model-config.ts:379)。resolve 从空配置出发逐条覆盖(config/model-config.ts:387):

  resolve(input: ModelConfigRuleResolutionInput): ModelConfig {
    let result = ModelConfig.empty();
    const baseUrl = input.baseUrl == null ? undefined : normalizeBaseURLForRuleMatch(input.baseUrl);
    for (const rule of this.#rules) {
      if (isExactModelRule(rule)) {
        if (rule.providerId !== input.providerId || rule.modelId !== input.modelId) continue;
  // ...
      if (rule.type === "template-model") {
        if (rule.templateId === input.templateId && rule.modelId === input.modelId)
          result = result.overlay(rule.config);
        continue;
      }
      // 只放宽推荐规则匹配,不改真实请求里的模型 ID。
      if (!matchesRule(rule.modelMatch, input.modelId, true)) continue;
      if (
        (rule.type === "model-api" || rule.type === "provider-site") &&
        rule.apiTypeMatch !== undefined
      ) {
        if (input.apiType == null || !matchesRule(rule.apiTypeMatch, input.apiType)) continue;
      }
      if (
        rule.type === "provider-site" &&
        (baseUrl === undefined || !matchesRule(rule.baseUrlMatch, baseUrl))
      )
        continue;
      result = result.overlay(rule.config);
    }
    return result;
  }

正则一律按 ^(?:pattern)$ 整串匹配,写入时就校验能否编译(rule-data-schema.ts:14);modelMatch 忽略大小写,协议与地址则区分大小写,地址匹配前会规范化主机大小写、默认端口和尾部斜杠(config/model-config.ts:544548)。

链条的起点是一条 .* 默认规则:启用、上下文 200000、只收文本、支持工具调用、输出上限 32000、推理档位只有 disabledenabled 且映射为空对象(zcode-builtin.json:867)。以个人 Coding Plan 上的 GLM-5.3 为例,后面依次命中:glm-5 家族规则(上下文 200000、输出 64000,zcode-builtin.json:918),glm-5.3 专属规则(上下文 1000000、档位改为 lowhighmax、输出 128000,zcode-builtin.json:978),三条 anthropic-messages 协议规则逐步把推理映射改写成 GLM-5.3 的形态(zcode-builtin.json:2594),站点规则再为 api.z.ai 打开图片、视频输入与原生 WebSearch(zcode-builtin.json:40124092),最后是一条精确启用规则。模型规则本身把 GLM-5.3 的图片输入写成 false,站点规则才把它打开;前端注释说明这是套餐端的服务端桥接,因此界面刻意不给它显示视觉徽标(packages/ui/src/lib/modelVisionBadge.ts:3)。

账号型 Provider 与模板

8 条 providerRules 是 z.ai 与 bigmodel 两个家族各四个账号型 Provider,访问方式都是 zhipu-account,协议都是 anthropic-messages

家族个人 Coding Plan团队 Coding PlanStart Plan闲时(Idle plan)
z.ai(zai-familyhttps://api.z.ai/api/anthropic同左https://zcode.z.ai/api/v1/zcode-plan/anthropichttps://zcode.z.ai/api/v1/off-peak/anthropic,隐藏
bigmodel(bigmodel-familyhttps://open.bigmodel.cn/api/anthropic同左与 z.ai 相同与 z.ai 相同,隐藏

各档套餐的模型、权益与凭据见账号、Coding Plan 与闲时计划。一条账号型规则的主体长这样(zcode-builtin.json:819):

          "providerId": "account:zai-offpeak-idle-plan",
          "providerName": "Z.AI Idle plan",
          "config": {
            "group": "zai-family",
            "builtinModelIds": ["GLM-5.3", "GLM-5.3-Flash"],
            "visibility": "hidden",
            "access": {
              "type": "zhipu-account",
              "mode": "off-peak",
              "accountType": "zai"
            },
            "api": {
              "type": "anthropic-messages",
              "baseUrl": "https://zcode.z.ai/api/v1/off-peak/anthropic"
            },

mode 只有 start-planindividual-coding-planteam-coding-planoff-peak 四种(packages/provider/src/config/provider-data-schema.ts:14)。

20 个模板是用户新建 Provider 时的起点,覆盖三种 API 类型 anthropic-messagesopenai-chat-completionsopenai-responsesprovider-data-schema.ts:4)。下表的“默认启用”按 templateModelRules 统计,其余模型列在模板里但默认关闭:

模板API 类型模型(默认启用/总数)
zai-apibigmodel-api(Z.ai、BigModel Coding Plan)anthropic-messages2/2
zai-standard-apibigmodel-standard-api(Z.ai、BigModel API)openai-chat-completions2/24
moonshot-kimiminimaxdeepseekxiaomi-mimoanthropic-messages3/6、3/8、2/2、2/2
qwen-alibaba-model-studio-cn-intl(阿里云百炼中国、国际)anthropic-messages、openai-chat-completions2/13、2/14
openaixaiopenai-responses4/10、2/3
anthropicanthropic-messages5/5
openrouteranthropic-messages11/59
opencode-go-chat-messages-responses(OpenCode Go)三种各一8/15、3/8、2/2
opencode-zen-chat-messages-responses(OpenCode Zen)三种各一10/14、5/15、2/14

模板的访问方式几乎都是普通 api-key,只有两个 Coding Plan 模板是 zhipu-coding-plan-api-keyprovider-data-schema.ts:32)。新建模板实例时,ID 取模板 ID 的小写短横线形式,冲突时加 -2-3 后缀;不带模板的自定义 Provider 从 new-provider 起名(packages/provider/src/config-service.ts:688)。与 OpenCode 依赖 models.dev、MiniMax Code 内置 models.dev 快照不同,ZCode 的模型目录完全由这份自维护的规则集给出。

随包分发与远程更新

构建期loadBuiltinProviderConfig 读取规则集(可用 ZCODE_BUILTIN_PROVIDER_CONFIG_FILE 换源),借 tsx 加载运行时同一个 decodeZCodeBuiltinRelease 做完整校验,再原样写进产物目录(scripts/builtin-provider-config.mjs:4370);构建环境 ZCODE_ENV 只能是 testproductionbuiltin-provider-config.mjs:35)。

启动时。CLI 在需要 Provider 的命令前准备路径:SEA 单文件里的资源键是 zcode-provider/zcode-builtin.json,释放到 ~/.zcode/v2/runtime/provider/bundled/zcode-builtin.json;普通安装则找入口旁的 provider/zcode-builtin.json 或仓库里的原文件(apps/zcode-cli/packages/cli/src/provider-runtime-env.ts:137149)。可更新的副本叫 Active 缓存,路径按平台、App 版本和 ZCode 控制面地址隔离(packages/provider-node/src/zcode-builtin-cache-paths.ts:24):

~/.zcode/v2/runtime/provider/<平台>/<App 版本>/endpoint-<地址 sha256 前 32 位>/zcode-builtin.json

随包基线与 Active 缓存并存时取 revision 大的;两者 revision 相同但内容不同,视为缓存损坏,以随包版本为准并重写缓存(packages/provider-node/src/zcode-builtin-provider-config-source.ts:180)。

远程同步。控制面地址取 ZCODE_BASE_URLZCODE_ENDPOINT_ORIGIN,缺省 https://zcode.z.aipackages/shared/src/zcodeEndpoint.ts:3142)。客户端先请求 GET /api/v1/client/configs(带 app_versionplatform),从 data.configs.builtin_provider_config_json 拿到 CDN 地址,再下载整份规则集(packages/provider-node/src/zcode-builtin-download.ts:5054)。

  • 配置运行时每 60 秒检查一次(packages/provider-node/src/provider-config-runtime.ts:120),真正下载受控制文件节流:成功后 1 小时内不再下载,失败按 60 秒起翻倍退避、最长 1 小时;多个进程靠控制文件上的 30 秒租约合并刷新,网络请求期间不持有文件锁(packages/provider-node/src/zcode-builtin-remote-synchronizer.ts:4896151)。
  • 两段请求共用 20 秒预算,正文上限 10000000 字节,请求不带凭据、不跟随重定向,CDN 地址必须是不带用户名密码的 https(zcode-builtin-download.ts:144379103)。
  • 应用时 revision 更小记为 stale,相同且内容一致为 unchanged,相同却内容不同直接报错,更大才写入(zcode-builtin-provider-config-source.ts:64);含已下线的 builtin:zapi 的整份 Release 会被拒绝(packages/provider-node/src/zcode-builtin-release.ts:43)。

README 把 ZCODE_BUILTIN_PROVIDER_CONFIG_FILE 描述为“本地 Provider 配置文件路径”(README.md:132)。从代码看它指的是 Built-in 规则集,不是个人配置;在独立运行的 CLI 里,效果还取决于是否同时设置了 ZCODE_PERSONAL_PROVIDER_CONFIG_FILE:两个都设时直接使用、不做远程同步;只设前者时它只是随包基线,Active 缓存里 revision 更大的远程版本仍会胜出(provider-runtime-env.ts:5886apps/zcode-cli/packages/bootstrap/src/app/process-provider-registry-runtime.ts:59)。

个人 Provider:provider_config.json

BYOK 配置、模型覆盖和默认模型选择都在同一个文件里:~/.zcode/v2/provider_config.jsonprovider-runtime-env.ts:76),数据基目录可用 ZCODE_DATA_BASE_DIR 改,文件路径可用 ZCODE_PERSONAL_PROVIDER_CONFIG_FILE 指定,但它必须与 Built-in 路径变量同时出现(packages/provider-node/src/runtime-paths.ts:21)。顶层结构由 packages/provider-node/src/provider-config-file-codec.ts:18 定义,下面是一个示意(字段按 schema,内容为虚构):

{
  "schemaVersion": 1,
  "config": {
    "providerOrder": ["deepseek"],
    "providerConfigRules": {
      "providerRules": [{
        "providerId": "deepseek", "templateId": "deepseek", "providerName": "DeepSeek",
        "config": {
          "group": "standard-personal",
          "access": { "type": "api-key", "apiKey": "<API Key>" },
          "personalModelIds": [], "modelOrder": []
        }
      }]
    },
    "modelConfigRules": { "providerModelRules": [], "manualProviderModelRules": [] },
    "defaultModelSelection": {
      "providerId": "deepseek", "modelId": "deepseek-v4-pro",
      "options": { "reasoningLevel": "max" }
    }
  }
}
  • API Key 明文存放:Key 是 access.apiKey 字段(provider-data-schema.ts:30),文件以 0600 权限原子写入,没有另做加密(packages/provider-node/src/personal-provider-config-repository.ts:148packages/shared/src/node/privateFilePersistence.ts:100)。账号凭据另存一处并逐项加密,见账号、Coding Plan 与闲时计划
  • 两种模型覆盖providerModelRules 是“推荐配置加稀疏覆盖”,manualProviderModelRules 是手动模式,必须给齐产品开放的可编辑叶子(上下文、结构化输出、原生搜索、会话中途 system、图片视频 PDF、推理档位与映射、输出上限),同一 Provider 与模型不能两种都声明(packages/provider/src/config/manual-model-config.ts:6rule-data-schema.ts:64)。
  • 读写:写入在文件锁内先规范化、再严格校验整份结果;多进程靠每秒一次的轮询发现外部修改(personal-provider-config-repository.ts:48143)。文件损坏时原样保留,本进程以空的个人层降级运行,等用户修复(personal-provider-config-repository.ts:159)。

模型选择:解析与持久化

一次模型选择就是 providerIdmodelId 加可选的 options.reasoningLevelpackages/shared/src/model-selection.ts:4)。TUI 里 /model <provider/model> 切模型,可以写成 provider/model$level 一并指定档位(src/model-selection.ts:3143);/effort <level>(别名 /variant)只改档位,/effort list 列出可选值(apps/zcode-cli/packages/cli/src/command-center/handlers/effort.ts:6)。两者成功后都把当前选择写回 defaultModelSelection,写失败只追加一条警告,不影响本会话已切换的模型(apps/zcode-cli/packages/cli/src/command-center/model-selection.ts:4)。

档位值来自有效模型配置的 reasoningLevel.values,约定按强度从低到高排列(packages/shared/src/model-config.ts:23)。用户主动选模型、没指定档位时取最高一档(apps/zcode-cli/packages/bootstrap/src/app/provider-registry-selection.ts:196);标题生成、记忆抽取、网页内容处理这类辅助调用固定取最低一档,输出不超过 5000 token(apps/zcode-cli/packages/core/src/model/auxiliary-model-options.ts:3)。

新会话的初始选择由 resolveInitialModelSelection 决定:配置的默认值可选、且档位合法才用;否则按 Registry 顺序找第一个可见模型并补最高档(packages/provider/src/model-selection-config.ts:2858)。zcode login 写下的默认值只有 providerIdmodelId、不带档位(apps/zcode-cli/packages/bootstrap/src/auth-login.ts:355),按这段逻辑会走 Registry 兜底;账号型 Provider 排在 Registry 最前,所以通常还是登录时那个模型,只是档位成了最高一档。恢复旧会话时不套用这套初始推荐(apps/zcode-cli/packages/bootstrap/src/app/runtime-config.ts:230)。

已有选择在执行前还要解析一次“有效选择”(packages/provider/src/effective-model-selection.ts:17):

  • 指向个人或团队 Coding Plan 的选择,会被换成当前账号唯一处于 current 的那个 Coding Plan Provider,没有或不止一个就报 account-connection-unavailableeffective-model-selection.ts:29)。Start Plan 按普通 Provider 处理,不参与这种换算(packages/provider-node/src/model-selection-facade.ts:17)。
  • 隐藏 Provider 只对闲时 Provider 放行(effective-model-selection.ts:44)。
  • 旧版本保存的档位 offnothink 会按一份精确匹配原规则的改名清单换成 disabled;个人配置改过这个模型的档位时不做换算(packages/provider-node/src/legacy-reasoning-level.ts:18packages/provider-node/src/legacy-reasoning-level-renames.ts:2)。旧的 builtin: 前缀 Provider ID 另有一次性迁移(packages/shared/src/legacy-model-provider-identity.ts:22)。

model-option-map:一门受限的 CEL

规则里的 reasoningLevel.mapmaxOutputTokens.map 是表达式字符串,求值结果是要合并进请求体的 JSON 补丁。代码把它叫 Restricted CEL,在 schema 校验时就编译一遍(packages/shared/src/model-config.ts:5),Model 绑定时再编译成可复用的程序,请求时只代入本轮冻结的值(packages/model-option-map/src/option-maps.ts:19)。

组件做什么
tokenizer标识符、单双引号字符串、JSON 安全的数字,运算符 &&!=>= 等与 + - * / % ! < >packages/model-option-map/src/tokenizer.ts:18
parser优先级由低到高:三元条件、逻辑或、逻辑与、相等、比较、加减、乘除、一元运算(packages/model-option-map/src/parser.ts:79
evaluator强类型求值:条件必须是布尔值,算术只接受数字,结果必须 JSON 安全(packages/model-option-map/src/evaluator.ts:147161
compiler按“变量名加源码”缓存,并要求顶层(含三元的两个分支)是对象(packages/model-option-map/src/compiler.ts:80
merge-patch按顺序把补丁合并进请求体,并检查两个选项是否写了同一路径(packages/model-option-map/src/merge-patch.ts:13

它刻意很小:唯一可用的变量就是选项本身,在 reasoningLevel 的映射里引用 maxOutputTokens 会报未知标识符;没有成员访问和函数调用;对象键必须是字符串字面量且不能重复(parser.ts:61159167198)。一条真实的规则,anthropic-messages 协议下所有模型的默认映射(zcode-builtin.json:2494):

        {
          "modelMatch": ".*",
          "apiTypeMatch": "anthropic-messages",
          "config": {
            "optionSpecs": {
              "reasoningLevel": {
                "map": "reasoningLevel == \"disabled\"\n  ? {\n      \"thinking\": {\n        \"type\": \"disabled\"\n      }\n    }\n  : {\n      \"thinking\": {\n        \"type\": \"adaptive\"\n      },\n      \"output_config\": {\n        \"effort\": reasoningLevel == \"enabled\" ? \"high\" : reasoningLevel\n      }\n    }"
              },
              "maxOutputTokens": {
                "map": "{'max_tokens': maxOutputTokens}"
              }
            }
          }
        },

packages/model-option-map 复制到临时目录,用规则集里的真实表达式求值,得到:

规则输入补丁
anthropic 默认disabled{"thinking":{"type":"disabled"}}
anthropic 默认enabled{"thinking":{"type":"adaptive"},"output_config":{"effort":"high"}}
anthropic 默认max{"thinking":{"type":"adaptive"},"output_config":{"effort":"max"}}
GLM-5.3 在 anthropic 下low{"thinking":{"type":"enabled"},"output_config":{"effort":"low"}}
chat-completions 默认high{"thinking":{"type":"enabled"},"enable_thinking":true,"reasoning_effort":"high","reasoning":{"effort":"high"}}
responses 默认disabled{"reasoning":{"effort":"none"}}
anthropic 输出上限128000{"max_tokens":128000}

chat-completions 的默认映射一次写四个字段,覆盖几家兼容服务各自的思考开关方言(zcode-builtin.json:2514);具体模型或站点再用后面的规则收窄。整个文件里一共只有 25 种不同的映射表达式。合并时两个选项的补丁按顺序叠到 SDK 生成的请求体上,并记下每个补丁写过的路径(merge-patch.ts:19):

  for (const namedPatch of patches) {
    const paths = collectWrittenPaths(namedPatch.patch);
    for (const path of paths) {
      const conflict = ownedPaths.find((owned) => pathsOverlap(owned.path, path));
      if (conflict) {
        throw new ModelOptionMapError(
          `Model option maps write conflicting JSON path ${formatPath(path)}: ${conflict.option} and ${namedPatch.option}`,
        );
      }
      ownedPaths.push({ option: namedPatch.option, path });
    }
    result = mergeObject(result, namedPatch.patch);
  }
  return result;
}

补丁里的 null 删除字段,对象递归合并,其余值覆盖(merge-patch.ts:63);所以 max_tokens 这类 SDK 已经写过的字段会被映射结果改写,而两个选项互相踩路径则直接报错。这个合并发生在适配层拦截的 fetch 里,请求体不是 JSON 文本时宁可失败也不静默跳过(apps/zcode-cli/packages/adapters/src/model/model-option-map-fetch.ts:38)。

下一篇:模型适配层——Registry 里的 Provider 与模型事实,怎样经由 Vercel AI SDK 变成一次真正的流式请求。

本页目录