/** * S-461 —— **模型 provider 词表的唯一属主**(闭集 + 路由腿的穷举判词 + 域外拒因)。 * * ## 病 * `MODEL_PROVIDER` 此前收**任意字符串**(`config.ts` 里就是一句 `process.env.MODEL_PROVIDER`),而消费它的 * 只有一处「非 anthropic 即 gateway」的二分。于是最自然的误填 —— 把**厂商名**写成 provider, * `MODEL_PROVIDER=deepseek` —— 起得来、静默走网关栈(实测改前读数:`model.provider="deepseek"`、 * `api="openai-completions"`)。运维以为自己点了一条专用路线,拿到的是默认腿,而读数上没有任何异常。 * 这与 [ref] 件⑤ / S-188 的三根模型线路旋钮是**同一条族律**([ref] A 档:坏值拒启,不静默折默认)。 * * ## 词源:取不到型,就取那句自述 + 一格钉 * 词表的上游属主是 `@sema-agent/settings-schema` 的 `ModelEntry.provider`。它的 zod 形是 * `z.string().min(1).default("gateway")` —— **没有可取的型**(与 `RemoteExecProvider` / `WebSearchProvider` * 那两张能取型的表不同),但它的 JSDoc 逐字写着三词与各自的腿: * > Routes the brain in the service (createRoutingBrain by provider): "gateway"/"vllm" → OpenAI brain, * > "anthropic" → Anthropic brain. * 于是本仓建闭集常量,并由 `test/config-coverage-model-plane.test.ts` 的 S-461 ④ 格**钉住它与那句自述同集** * (读的是装载的那份 dist,不手抄第二份词表)。上游哪天把词表写进 zod enum,那一格会告诉我们该改成取型。 * * ## 三条腿各自的边界(刻意分开写) * · {@link isModelProvider} / {@link modelProviderRefusal} —— **env 面**的闭集判据与拒因。它管的是 * `MODEL_PROVIDER`(以及缺省推断产出的那个词),执法点在 `config.ts` 的模型面装配 + `config-invariants.ts` * 的 `model-provider-word` 条(热路径同判,不是第二份判据); * · {@link modelBrainLane} —— **穷举 switch**([ref]:词表是闭集,加一词不补臂 = 编译红); * · {@link catalogBrainLane} —— **目录词**那一侧。见它自己的头注:目录条目的 provider **不过** boot 闸, * 所以那里必须有一条「认不出」的臂,而那条臂是**既有语义的书面化**,不是本批新开的兜底。 */ /** * `MODEL_PROVIDER` 的闭集。**声明序 = 语义序**:两条 OpenAI 兼容腿在前,Anthropic 腿在后 * (与上游那句自述的行文同序,方便逐字对读)。 */ export declare const MODEL_PROVIDER_WORDS: readonly ["gateway", "vllm", "anthropic"]; /** 闭集成员型。加一词 ⇒ {@link modelBrainLane} 的穷举 switch 当场编译红。 */ export type ModelProvider = (typeof MODEL_PROVIDER_WORDS)[number]; /** 这个词在闭集里吗(`undefined` / 空串一律 false —— 「未设」不是一个 provider,由调用点自己折缺省)。 */ export declare function isModelProvider(word: string | undefined): word is ModelProvider; /** * 域外值的**拒因**(`undefined` = 这个词合法)。 * * 🔴 **机读码 `config.model_provider_invalid` 逐字在句子里**(clay 令 2026-09-18):拒启原话是运维与壳 * 唯一拿得到的东西,一句纯散文只能靠人读。码的拼法随 `config.<键名蛇形>_invalid` 既有族 * (`config.retention_policy_invalid` / `config.execution_env_home_dir_invalid` / `config.placement_root_invalid`), * 方括号形与本仓既有那一处(`plugins/remote-env-device.ts`)逐字同形。**出现位置**:boot 期 `loadConfig()` * 抛出的 Error.message ⇒ 进程以非零退出,这句话落在 **stderr 首屏**(未捕获异常的栈首行)。 * * 拒因三件齐:**码** + **违例值**(照原样带引号,前后空格/大小写一眼可见) + **闭集三词**。 */ export declare function modelProviderRefusal(word: string): string | undefined; /** 一只 provider 词走哪条 brain 腿。闭集 ⇒ 两个值,没有第三种。 */ export type ModelBrainLane = "anthropic" | "gateway"; /** * **穷举判词**([ref]:词表是闭集,加一词而不补臂是 COMPILE 错,不是运行期漏判)。 * * `"vllm"` 与 `"gateway"` 同腿**不是**兜底,是上游那句自述的逐字转写(两者都是 OpenAI 兼容端点, * 差别在部署形而不在协议);它们哪天要分家,分的是这张表,而不是某个调用点的 `if`。 */ export declare function modelBrainLane(provider: ModelProvider): ModelBrainLane; /** * **目录条目**(`config.models[*].provider`)那一侧的判词。 * * 🔴 为什么它与 {@link modelBrainLane} 不是同一只函数:两者的**输入保证不同**。 * · `MODEL_PROVIDER` 走 boot 闸(域外拒启)⇒ 到达 {@link modelBrainLane} 的恒是闭集词; * · **目录条目**来自 settings-schema 的 `z.string()`,可以从 config-center **热应用**进来 —— 那条路上 * 没有、也不该有「拒启」这回事(一次热发布不能让在跑的副本死),所以这里必然存在一条「认不出」的臂。 * * 🔴 那条臂**就是今天的语义,本批一个字节都没改**([ref]「不可避免的运行期 miss 臂必须说出来」): * core 的 `createRoutingBrain({anthropic}, {fallback: gatewayStack})` 本身就把一切非 `anthropic` 的词 * 路由到网关栈(`brain.ts` 顶注那句 "everything else → the local gateway stack (also the fallback for an * unrecognized provider)" 是它的原话)。本函数做的只是把那句散文变成**一个有名字的判词**,于是 * 「目录里写了个没人认识的 provider」这件事在代码里有一处可指的坐标,而不是散在两个 `=== "anthropic"` 里。 * * ⚠️ **未闭环**(S-461 收车档登记):目录词今天仍然**无声**地落进网关腿。要让它也响亮,需要的是一条 * **热路径上的**告警/拒收(config-center 应用期逐条校验 + 运维可读的拒因),那是另一件事、另一台车 —— * 在这里加一个 throw 会让一次热发布打死在跑的副本,方向反了。 */ export declare function catalogBrainLane(provider: string | undefined): ModelBrainLane; //# sourceMappingURL=model-provider.d.ts.map