mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-14 03:56:24 +08:00
* fix(codex): route OpenCode Zen reasoning effort through the platform config Zen 网关的合法 reasoning_effort 档位是逐模型的(models.dev reasoning_options:glm-5.2 仅 high|max、deepseek-v4-flash 为 low|high|max、kimi-k3 仅 max,glm-5.1 等为 toggle 型无 effort), opencode 客户端也严格按模型声明发值。此前统一并集映射会把 Codex 默认的 medium 发给只声明 high|max 的 glm-5.2(默认路径必现), 也会把 low 发给不支持 low 的 deepseek-v4-pro(回归)。 - infer_aggregator_platform_config 新增 opencode.ai 平台条目(仅按 name+base_url 判定,不看模型名),zen 模式:顶层 reasoning_effort + reasoning_content,不发厂商原生 thinking 形状 - CodexChatReasoningConfig 新增运行时字段 effort_levels(#[serde(skip)] 不持久化);resolve 末端对 zen 模式按请求模型从供应商 settings_config.modelCatalog 的 reasoningLevels(复用 #6228 引入的 逐模型声明,camelCase/snake_case 双格式兼容)查表附上,查不到则 None —— 转换层完全不发 reasoning_effort - transform_codex_chat 的 zen 映射改为逐模型钳制:取「不小于请求的 最近合法档」,超出最高档取最高档;请求值无法识别则丢弃字段 - OpenCode Go 预设按 models.dev 补上逐模型 reasoningLevels 表; 表单加载侧提取纯函数 mapCodexCatalogModelForForm 并保住 reasoningLevels 的 load→save 回环(含 snake_case 旧数据),保存侧 trim 档位值 存量供应商不会自动获得档位表(其 settings_config 无 reasoningLevels), 行为退化为不发 effort 字段(与改动前一致的安全默认);重新添加一次 OpenCode Go 预设即可获得逐模型表。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(codex): move OpenCode Go to filled entries in reasoning-level expectations 上游 #6228 的 deliberately-unfilled 清单把 OpenCode Go 整体列为不填, 依据是「无 codexChatReasoning 声明、逐模型语义未经网关验证」——两个 前提均被本 PR 改变:预设已补 codexChatReasoning(effortValueMode:"zen"), 且逐模型档位有据(opencode 客户端 variants() 严格按 models.dev reasoning_options 发值)。glm-5.2 / deepseek-v4-pro / deepseek-v4-flash 移入 EXPECTED(不填 none:effort 档位非思考开关);glm-5.1(toggle 型)、 kimi-k2.7-code(官方标注不支持 effort)、mimo-v2.5-pro(models.dev 未 收录)保持不填并更新注释依据。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
120 lines
4.2 KiB
TypeScript
120 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { normalizeCodexCatalogModelsForSave } from "@/components/providers/forms/ProviderForm";
|
||
import { mapCodexCatalogModelForForm } from "@/components/providers/forms/hooks/useCodexConfigState";
|
||
|
||
describe("ProviderForm Codex catalog helpers", () => {
|
||
it("normalizes catalog rows and removes empty or duplicate models", () => {
|
||
expect(
|
||
normalizeCodexCatalogModelsForSave([
|
||
{ model: " deepseek-v4-flash ", displayName: " DeepSeek " },
|
||
{ model: "deepseek-v4-flash", displayName: "Duplicate" },
|
||
{ model: "", displayName: "Empty" },
|
||
{ model: "kimi-k2", contextWindow: "128000 tokens" },
|
||
]),
|
||
).toEqual([
|
||
{ model: "deepseek-v4-flash", displayName: "DeepSeek" },
|
||
{ model: "kimi-k2", contextWindow: 128000 },
|
||
]);
|
||
});
|
||
|
||
it("preserves native-profile overrides (parallel tool calls + input modalities + base instructions)", () => {
|
||
expect(
|
||
normalizeCodexCatalogModelsForSave([
|
||
{
|
||
model: "MiniMax-M3",
|
||
displayName: "MiniMax-M3",
|
||
contextWindow: 1000000,
|
||
supportsParallelToolCalls: true,
|
||
inputModalities: ["text", "image"],
|
||
baseInstructions:
|
||
" You are Codex, a coding agent based on MiniMax-M3. ",
|
||
},
|
||
// false must be preserved (not dropped as falsy); empty modalities dropped;
|
||
// empty/whitespace baseInstructions dropped
|
||
{
|
||
model: "mimo-v2.5-pro",
|
||
supportsParallelToolCalls: false,
|
||
inputModalities: [],
|
||
baseInstructions: " ",
|
||
},
|
||
]),
|
||
).toEqual([
|
||
{
|
||
model: "MiniMax-M3",
|
||
displayName: "MiniMax-M3",
|
||
contextWindow: 1000000,
|
||
supportsParallelToolCalls: true,
|
||
inputModalities: ["text", "image"],
|
||
baseInstructions: "You are Codex, a coding agent based on MiniMax-M3.",
|
||
},
|
||
{ model: "mimo-v2.5-pro", supportsParallelToolCalls: false },
|
||
]);
|
||
});
|
||
|
||
it("preserves per-model reasoning levels and default level", () => {
|
||
expect(
|
||
normalizeCodexCatalogModelsForSave([
|
||
{
|
||
model: "deepseek-v4-flash",
|
||
displayName: "DeepSeek V4 Flash",
|
||
reasoningLevels: ["none", "low", "medium", "high", "xhigh", "max"],
|
||
defaultReasoningLevel: " xhigh ",
|
||
},
|
||
// empty levels / whitespace default are dropped
|
||
{
|
||
model: "plain-model",
|
||
reasoningLevels: [],
|
||
defaultReasoningLevel: " ",
|
||
},
|
||
]),
|
||
).toEqual([
|
||
{
|
||
model: "deepseek-v4-flash",
|
||
displayName: "DeepSeek V4 Flash",
|
||
reasoningLevels: ["none", "low", "medium", "high", "xhigh", "max"],
|
||
defaultReasoningLevel: "xhigh",
|
||
},
|
||
{ model: "plain-model" },
|
||
]);
|
||
});
|
||
|
||
it("round-trips reasoning levels through load and save without loss", () => {
|
||
// load→save 回环:加载映射(mapCodexCatalogModelForForm)与保存归一化
|
||
// (normalizeCodexCatalogModelsForSave)各锁半边时,回环丢字段两边都测不出——
|
||
// 而编辑保存丢表会让依赖逐模型档位的功能(zen 钳制)静默失效且 UI 无可察觉。
|
||
const stored = [
|
||
{
|
||
model: "glm-5.2",
|
||
displayName: "GLM 5.2",
|
||
reasoningLevels: ["high", "max"],
|
||
},
|
||
// 手写/旧数据可能是 snake_case,加载侧兼容后保存侧同样要留住
|
||
{ model: "deepseek-v4-flash", reasoning_levels: ["low", "high", "max"] },
|
||
{ model: "glm-5.1" }, // toggle 型:无表,全程不得凭空造表
|
||
];
|
||
|
||
const roundTripped = normalizeCodexCatalogModelsForSave(
|
||
stored.map(mapCodexCatalogModelForForm),
|
||
);
|
||
|
||
expect(roundTripped).toEqual([
|
||
{
|
||
model: "glm-5.2",
|
||
displayName: "GLM 5.2",
|
||
reasoningLevels: ["high", "max"],
|
||
},
|
||
{ model: "deepseek-v4-flash", reasoningLevels: ["low", "high", "max"] },
|
||
{ model: "glm-5.1" },
|
||
]);
|
||
});
|
||
|
||
it("trims reasoning level values on save", () => {
|
||
// 手编 JSON 里的 " high " 不得原样落库/发给上游。
|
||
expect(
|
||
normalizeCodexCatalogModelsForSave([
|
||
{ model: "glm-5.2", reasoningLevels: [" high ", "max"] },
|
||
]),
|
||
).toEqual([{ model: "glm-5.2", reasoningLevels: ["high", "max"] }]);
|
||
});
|
||
});
|