diff --git a/open-sse/services/roleNormalizer.ts b/open-sse/services/roleNormalizer.ts index ebf1bab32a..7f63d2dd2f 100644 --- a/open-sse/services/roleNormalizer.ts +++ b/open-sse/services/roleNormalizer.ts @@ -57,6 +57,15 @@ const MODELS_WITHOUT_SYSTEM_ROLE = [ "ernie-", // Baidu ERNIE models ]; +const PROVIDER_SCOPED_MODELS_WITHOUT_SYSTEM_ROLE: Record = { + // ZenMux exposes Z.AI GLM through OpenAI-compatible model ids such as + // "z-ai/glm-5.2". Z.AI rejects compressed histories that start with a + // system summary followed by an assistant/tool bundle, while OpenRouter + // tolerates the same shape. Treat these vendor-prefixed GLM ids like native + // GLM so normalizeSystemRole moves system/developer content into a user turn. + zenmux: [/(?:^|\/)glm(?:-|$)/i], +}; + interface MessageContentPart { type?: string; text?: string; @@ -88,9 +97,15 @@ function extractTextFromContent(content: unknown): string { * Check if a provider+model combo supports the system role. */ function supportsSystemRole(provider: string, model: string): boolean { - if (PROVIDERS_WITHOUT_SYSTEM_ROLE.has(provider)) return false; + const providerLower = (provider || "").trim().toLowerCase(); + if (PROVIDERS_WITHOUT_SYSTEM_ROLE.has(providerLower)) return false; const modelLower = (model || "").toLowerCase(); + + for (const pattern of PROVIDER_SCOPED_MODELS_WITHOUT_SYSTEM_ROLE[providerLower] ?? []) { + if (pattern.test(modelLower)) return false; + } + for (const prefix of MODELS_WITHOUT_SYSTEM_ROLE) { if (modelLower.startsWith(prefix)) return false; } diff --git a/tests/unit/role-normalizer.test.ts b/tests/unit/role-normalizer.test.ts index b45780ca3f..038ab20d45 100644 --- a/tests/unit/role-normalizer.test.ts +++ b/tests/unit/role-normalizer.test.ts @@ -99,6 +99,35 @@ test("normalizeSystemRole inserts a user message when no user exists and drops e ]); }); +test("normalizeSystemRole treats ZenMux z-ai/glm models as GLM even with vendor prefix", () => { + const messages = [ + { role: "system", content: "[Context compressed: earlier messages removed]" }, + { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "read", arguments: "{}" }, + }, + ], + }, + { role: "tool", tool_call_id: "call_1", content: "ok" }, + ]; + + const result = normalizeSystemRole(messages, "zenmux", "z-ai/glm-5.2"); + + assert.deepEqual(result, [ + { + role: "user", + content: "[System Instructions]\n[Context compressed: earlier messages removed]", + }, + messages[1], + messages[2], + ]); +}); + test("normalizeRoles composes model, developer and system normalization in order", () => { const messages = [ { role: "model", content: "first answer" },