diff --git a/crates/agent-gateway/test/webui/web-settings.test.mjs b/crates/agent-gateway/test/webui/web-settings.test.mjs index d056c9e83..c7fa59057 100644 --- a/crates/agent-gateway/test/webui/web-settings.test.mjs +++ b/crates/agent-gateway/test/webui/web-settings.test.mjs @@ -6,6 +6,7 @@ const loader = createWebModuleLoader(); const webSettings = loader.loadModule("src/lib/webSettings.ts"); const settings = loader.loadModule("@/lib/settings/index.ts"); const settingsSync = loader.loadModule("@liveagent/ui/lib/settings/sync.ts"); +const catalog = loader.loadModule("@liveagent/ui/lib/models/modelCatalog.ts"); const chatHelpers = loader.loadModule("@/lib/chat/chatPageHelpers.ts"); const adminApi = loader.loadModule("@/lib/adminApi.ts"); const RIGHT_DOCK_TAB_IDS = settings.RIGHT_DOCK_SINGLETON_TAB_IDS; @@ -1879,21 +1880,32 @@ test("xai model limits use the generated catalog without changing thinking detec }); test("xai thinking levels come from the catalog per model, thinking always on", () => { - // 档位按型号差异化(目录真值):grok-4.5 只有 low/medium/high; - // grok-4.20-multi-agent-0309 声明到 xhigh。xai 思考一律恒开 - //(wire 无法表达 off),目录的 off 声明对 xai 供应商不生效。 - assert.deepEqual(settings.getKnownModelThinkingLevels("xai", "grok-4.5"), [ - "low", - "medium", - "high", - ]); - assert.equal(settings.isThinkingAlwaysOnForModel("xai", "grok-4.5"), true); - const multiAgent = settings.getKnownModelThinkingLevels("xai", "grok-4.20-multi-agent-0309"); - assert.ok(multiAgent.includes("xhigh")); - assert.equal(settings.isThinkingAlwaysOnForModel("xai", "grok-4.3"), true); - // 钳制路径:xhigh 超出 grok-4.5 档位表时压回默认 high。 + // 档位按型号取目录真值:逐条对照快照里每个 xai 条目,不写死档位表(目录每日 + // 自动刷新)。单点对照分不出"按型号读目录"和"整个供应商一张固定表",逐条 + // 对照可以(快照里有档位为空的条目)。xai 思考一律恒开(wire 无法表达 off), + // 目录里 off: true 的条目对 xai 供应商同样不生效。 + const entries = catalog.MODEL_CATALOG.xai.filter((model) => model.thinking); + assert.ok(entries.length >= 2, "xai thinking entries missing from the catalog snapshot"); + for (const entry of entries) { + assert.deepEqual( + settings.getKnownModelThinkingLevels("xai", entry.id), + [...entry.thinking.levels], + `${entry.id} levels`, + ); + assert.equal( + settings.isThinkingAlwaysOnForModel("xai", entry.id), + true, + `${entry.id} always on`, + ); + } + // 钳制路径:表外档位(取档位阶梯里最高的表外档)压回默认 high。 + const grok45Levels = settings.getKnownModelThinkingLevels("xai", "grok-4.5"); + const outside = ["minimal", "low", "medium", "high", "xhigh", "max"] + .filter((level) => !grok45Levels.includes(level)) + .at(-1); + assert.ok(outside, "grok-4.5 covers the whole level ladder; clamp needs another model"); const clamped = settings.normalizeChatRuntimeControlsForProvider( - { reasoning: "xhigh", reasoningByProvider: { xai: "xhigh" } }, + { reasoning: outside, reasoningByProvider: { xai: outside } }, { providerId: "xai", modelId: "grok-4.5" }, ); assert.equal(clamped.reasoning, "high"); diff --git a/crates/agent-gui/test/models/model-catalog.test.mjs b/crates/agent-gui/test/models/model-catalog.test.mjs index 8b35deeae..10862be11 100644 --- a/crates/agent-gui/test/models/model-catalog.test.mjs +++ b/crates/agent-gui/test/models/model-catalog.test.mjs @@ -172,7 +172,11 @@ test("normalizeModelIdCandidates strips a month-day date suffix only when no ful test("a dated relay id resolves to the catalog entry, an exact dated id keeps its own entry", () => { const relayed = catalog.findCatalogModelAcrossProviders("deepseek-ai/deepseek-v4-pro-0813"); assert.equal(relayed?.id, "deepseek-v4-pro"); - assert.equal(relayed?.contextWindow, 1000000); + // 限额对照目录快照本身,不写死数值:目录每日自动刷新会改动上游限额。 + assert.deepEqual( + relayed, + catalog.MODEL_CATALOG.deepseek.find((model) => model.id === "deepseek-v4-pro"), + ); assert.equal(catalog.findCatalogModelAcrossProviders("deepseek-r1-0528")?.id, "deepseek-r1-0528"); assert.equal(catalog.findCatalogModelAcrossProviders("deepseek-r1-0601")?.id, "deepseek-r1"); }); diff --git a/crates/agent-gui/test/settings/normalization.test.mjs b/crates/agent-gui/test/settings/normalization.test.mjs index 04b33b20d..821420725 100644 --- a/crates/agent-gui/test/settings/normalization.test.mjs +++ b/crates/agent-gui/test/settings/normalization.test.mjs @@ -6,8 +6,21 @@ const loader = createTsModuleLoader(); const settings = loader.loadModule("src/lib/settings/index.ts"); const normalize = loader.loadModule("@liveagent/ui/lib/settings/normalize.ts"); const sync = loader.loadModule("@liveagent/ui/lib/settings/sync.ts"); +const modelCatalog = loader.loadModule("@liveagent/ui/lib/models/modelCatalog.ts"); const RIGHT_DOCK_TAB_IDS = settings.RIGHT_DOCK_SINGLETON_TAB_IDS; +// 目录每日自动刷新会改动上游限额:断言"取到目录真值"的用例对照快照条目, +// 不写死数值。 +function catalogLimits(section, modelId) { + const entry = modelCatalog.MODEL_CATALOG[section].find((model) => model.id === modelId); + assert.ok(entry, `${section}/${modelId} missing from the catalog snapshot`); + return { contextWindow: entry.contextWindow, maxOutputToken: entry.maxOutputToken }; +} + +function pickLimits(limits) { + return { contextWindow: limits.contextWindow, maxOutputToken: limits.maxOutputToken }; +} + async function withNavigator(value, task) { const previous = Object.getOwnPropertyDescriptor(globalThis, "navigator"); Object.defineProperty(globalThis, "navigator", { @@ -315,8 +328,11 @@ test("DeepSeek provider normalization keeps native routing and native search", ( assert.equal(provider.promptCachingEnabled, false); assert.equal(provider.promptCacheHintMode, undefined); assert.equal(provider.nativeWebSearchEnabled, true); - assert.equal(provider.models[0].contextWindow, 1_000_000); - assert.equal(provider.models[0].maxOutputToken, 393_216); + assert.deepEqual(pickLimits(provider.models[0]), catalogLimits("deepseek", "deepseek-v4-flash")); + assert.notDeepEqual( + pickLimits(provider.models[0]), + modelCatalog.PROVIDER_FALLBACK_LIMITS.deepseek, + ); }); test("legacy Codex-group DeepSeek configs stay untouched — migration is user-driven", () => { @@ -3044,8 +3060,14 @@ test("cross-provider models resolve real catalog limits instead of provider fall "claude_code", "deepseek-v4-flash", ); - assert.equal(deepseekUnderClaude.contextWindow, 1_000_000); - assert.equal(deepseekUnderClaude.maxOutputToken, 393_216); + assert.deepEqual( + pickLimits(deepseekUnderClaude), + catalogLimits("deepseek", "deepseek-v4-flash"), + ); + assert.notDeepEqual( + pickLimits(deepseekUnderClaude), + modelCatalog.PROVIDER_FALLBACK_LIMITS.claude_code, + ); const glmUnderCodex = settings.getProviderModelDefaults("codex", "glm-4.7"); assert.equal(glmUnderCodex.contextWindow, 204_800); assert.equal(glmUnderCodex.maxOutputToken, 131_072); diff --git a/crates/agent-ui/src/lib/models/catalog.generated.ts b/crates/agent-ui/src/lib/models/catalog.generated.ts index 101faf051..439a83623 100644 --- a/crates/agent-ui/src/lib/models/catalog.generated.ts +++ b/crates/agent-ui/src/lib/models/catalog.generated.ts @@ -26,7 +26,7 @@ export type CatalogModelEntry = { export type CatalogProviderId = "anthropic" | "google" | "openai" | "xai" | "deepseek" | "zhipuai" | "moonshotai" | "minimax" | "stepfun" | "xiaomi" | "longcat" | "alibaba" | "tencent"; -export const MODEL_CATALOG_SNAPSHOT_DATE = "2026-10-02"; +export const MODEL_CATALOG_SNAPSHOT_DATE = "2026-11-02"; export const MODEL_CATALOG: Record = { anthropic: [ @@ -34,6 +34,7 @@ export const MODEL_CATALOG: Record