fix(nvidia): restore GLM-5.2 reasoning on NIM (#7215) (#7296)

* fix(nvidia): map GLM-5.2 reasoning to thinking toggle

Fixes #7215.

* fix(nvidia): shrink default.ts under the file-size ratchet

The GLM-5.2 reasoning-mapping call in requestBodyDefaults() pushed
open-sse/executors/default.ts from 877 to 881 lines, tripping the
frozen check:file-size ceiling (Fast Quality Gates). withDefaults is
typed unknown, so the `as typeof withDefaults` cast added by the
multi-line call was unnecessary — collapsing to a single-line call
removes the cast and the line-wrap, landing the file at 876 lines.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* refactor(nvidia): extract mapNvidiaGlm52ReasoningParams helpers to clear complexity ratchet

mapNvidiaGlm52ReasoningParams landed at cyclomatic complexity 24 (limit
15), a brand-new violation that pushed the project-wide complexity
ratchet from 2056 to 2057 (Fast Quality Gates: check:complexity-ratchets).
It was previously masked by the file-size failure aborting the job
before this step ran.

Split the function into three single-purpose helpers — effort
extraction, chat_template_kwargs construction, and the
reasoning_effort/reasoning.effort strip — bringing the orchestrating
function's complexity back under threshold with no behavior change
(all 41 cases in tests/unit/base-executor-sanitize-effort.test.ts
still pass unchanged).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(nvidia): restore default executor file-size gate

---------

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
This commit is contained in:
backryun
2026-07-20 22:07:55 +09:00
committed by GitHub
parent 4b06761ad5
commit eebf15f3d0
3 changed files with 218 additions and 6 deletions

View File

@@ -38,6 +38,105 @@ export const MISTRAL_NO_REASONING_EFFORT_PATTERN = /devstral/i;
// Order matters: the opt-in check must run BEFORE the broad Claude/haiku/oswe strip.
export const GITHUB_REASONING_EFFORT_OPT_IN_PATTERN = /claude[-_.]?(?:opus|sonnet)[-_.]?4[-_.]6/i;
export const GITHUB_NO_REASONING_EFFORT_PATTERN = /(claude|haiku|oswe)/i;
const NVIDIA_GLM_52_PATTERN = /z-ai\/glm-5\.2\b/i;
type ReasoningSanitizeLog = {
info?: (tag: string, msg: string) => void;
};
function isNvidiaGlm52(provider: string, model: string | undefined): boolean {
return provider === "nvidia" && NVIDIA_GLM_52_PATTERN.test(model || "");
}
type NvidiaGlm52EffortInfo = {
reasoning: Record<string, unknown> | null;
effortStr: string;
};
/** Pulls a normalized (lowercased) effort string out of top-level or nested `reasoning.effort`. */
function extractNvidiaGlm52Effort(b: Record<string, unknown>): NvidiaGlm52EffortInfo | null {
const reasoning =
b.reasoning && typeof b.reasoning === "object" && !Array.isArray(b.reasoning)
? (b.reasoning as Record<string, unknown>)
: null;
const effort = b.reasoning_effort ?? reasoning?.effort;
if (effort === undefined) return null;
const effortStr = typeof effort === "string" ? effort.toLowerCase() : "";
if (!effortStr) return null;
return { reasoning, effortStr };
}
/** Builds `chat_template_kwargs.enable_thinking`, or null when the existing kwargs shape is unusable. */
function buildNvidiaGlm52TemplateKwargs(
rawTemplateKwargs: unknown,
effortStr: string
): Record<string, unknown> | null {
if (
rawTemplateKwargs !== undefined &&
(!rawTemplateKwargs ||
typeof rawTemplateKwargs !== "object" ||
Array.isArray(rawTemplateKwargs))
) {
return null;
}
const templateKwargs = {
...((rawTemplateKwargs as Record<string, unknown> | undefined) ?? {}),
};
if (!Object.prototype.hasOwnProperty.call(templateKwargs, "enable_thinking")) {
templateKwargs.enable_thinking = effortStr !== "none";
}
return templateKwargs;
}
/** Returns a copy of `b` with `reasoning_effort`/`reasoning.effort` replaced by `templateKwargs`. */
function withNvidiaGlm52TemplateKwargs(
b: Record<string, unknown>,
templateKwargs: Record<string, unknown>,
reasoning: Record<string, unknown> | null
): Record<string, unknown> {
const next: Record<string, unknown> = { ...b, chat_template_kwargs: templateKwargs };
delete next.reasoning_effort;
if (reasoning) {
const nextReasoning = { ...reasoning };
delete nextReasoning.effort;
if (Object.keys(nextReasoning).length === 0) delete next.reasoning;
else next.reasoning = nextReasoning;
}
return next;
}
/**
* Map OmniRoute's reasoning-effort inputs onto the binary thinking switch exposed by
* NVIDIA's hosted GLM-5.2 chat template. This runs before DefaultExecutor's unsupported
* parameter stripping so a nested `reasoning.effort` is not discarded first, and is also
* reused by the final provider sanitizer for non-default execution paths.
*/
export function mapNvidiaGlm52ReasoningParams(
body: unknown,
provider: string,
model: string | undefined,
log?: ReasoningSanitizeLog | null
): unknown {
if (!isNvidiaGlm52(provider, model)) return body;
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
const b = body as Record<string, unknown>;
const info = extractNvidiaGlm52Effort(b);
if (!info) return body;
const templateKwargs = buildNvidiaGlm52TemplateKwargs(b.chat_template_kwargs, info.effortStr);
if (!templateKwargs) return body;
const next = withNvidiaGlm52TemplateKwargs(b, templateKwargs, info.reasoning);
log?.info?.(
"REASONING_SANITIZE",
`nvidia/${model || ""}: mapped reasoning effort to enable_thinking`
);
return next;
}
export function supportsMaxEffortForProvider(provider: string, model: string): boolean {
const isClaude =
@@ -142,8 +241,12 @@ export function sanitizeReasoningEffortForProvider(
body: unknown,
provider: string,
model: string | undefined,
log?: { info?: (tag: string, msg: string) => void } | null
log?: ReasoningSanitizeLog | null
): unknown {
if (isNvidiaGlm52(provider, model)) {
return mapNvidiaGlm52ReasoningParams(body, provider, model, log);
}
if (!body || typeof body !== "object" || Array.isArray(body)) return body;
const b = body as Record<string, unknown>;
const c = readEffortCarriers(b);

View File

@@ -1,4 +1,5 @@
import { BaseExecutor, type ExecuteInput } from "./base.ts";
import { mapNvidiaGlm52ReasoningParams } from "./base/reasoningEffort.ts";
import { PROVIDERS, OAUTH_ENDPOINTS } from "../config/constants.ts";
import { getAccessToken } from "../services/tokenRefresh.ts";
@@ -701,14 +702,13 @@ export class DefaultExecutor extends BaseExecutor {
);
}
// Config-driven strip of params unsupported by the target provider/model
// (e.g. claude-opus-4 deprecated `temperature` → Anthropic 400). Port from
// 9router#7ae9fff6 (fixes upstream #1748). Rules live in
// ../translator/paramSupport.ts so adding one means editing one table.
// Strip params unsupported by the target provider/model before sending upstream.
// Rules live in ../translator/paramSupport.ts (9router#7ae9fff6; fixes #1748).
if (typeof withDefaults === "object" && withDefaults !== null) {
const bodyRecord = withDefaults as Record<string, unknown>;
const outboundModel = typeof bodyRecord.model === "string" ? bodyRecord.model : model;
stripUnsupportedParams(this.provider, outboundModel, bodyRecord);
withDefaults = mapNvidiaGlm52ReasoningParams(bodyRecord, this.provider, outboundModel);
stripUnsupportedParams(this.provider, outboundModel, withDefaults as Record<string, unknown>);
}
// Apply modelIdPrefix from RegistryEntry (e.g. "accounts/fireworks/models/")

View File

@@ -2,6 +2,7 @@ import test from "node:test";
import assert from "node:assert/strict";
const { sanitizeReasoningEffortForProvider } = await import("../../open-sse/executors/base.ts");
const { DefaultExecutor } = await import("../../open-sse/executors/default.ts");
function makeLog() {
const messages: Array<[string, string]> = [];
@@ -394,6 +395,114 @@ test("sanitizeReasoningEffortForProvider: non-object body returns unchanged", ()
assert.equal(sanitizeReasoningEffortForProvider(arr, "xiaomi-mimo", "x", null), arr);
});
// ── NVIDIA NIM GLM-5.2 (#7215) ─────────────────────────────────────────────
test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 enables thinking for active effort", () => {
for (const effort of ["low", "medium", "high", "xhigh", "max"]) {
const body = { reasoning_effort: effort, messages: [] };
const result = sanitizeReasoningEffortForProvider(
body,
"nvidia",
"z-ai/glm-5.2",
null
) as Record<string, unknown>;
assert.notEqual(result, body);
assert.equal(result.reasoning_effort, undefined);
assert.deepEqual(result.chat_template_kwargs, { enable_thinking: true });
}
});
test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 maps none to thinking off", () => {
const result = sanitizeReasoningEffortForProvider(
{ reasoning_effort: "none", messages: [] },
"nvidia",
"z-ai/glm-5.2",
null
) as Record<string, unknown>;
assert.equal(result.reasoning_effort, undefined);
assert.deepEqual(result.chat_template_kwargs, { enable_thinking: false });
});
test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 maps nested reasoning.effort", () => {
const result = sanitizeReasoningEffortForProvider(
{ reasoning: { effort: "high" }, messages: [] },
"nvidia",
"z-ai/glm-5.2",
null
) as Record<string, unknown>;
assert.equal(result.reasoning, undefined);
assert.deepEqual(result.chat_template_kwargs, { enable_thinking: true });
});
test("DefaultExecutor: NVIDIA GLM-5.2 maps nested effort before unsupported-param stripping", () => {
const result = new DefaultExecutor("nvidia").transformRequest(
"z-ai/glm-5.2",
{
model: "z-ai/glm-5.2",
reasoning: { effort: "high", summary: "auto" },
messages: [],
},
false,
{}
) as Record<string, unknown>;
assert.equal(result.reasoning, undefined);
assert.equal(result.reasoning_effort, undefined);
assert.deepEqual(result.chat_template_kwargs, { enable_thinking: true });
});
test("DefaultExecutor: NVIDIA reasoning levels remain intact for GPT-OSS", () => {
const result = new DefaultExecutor("nvidia").transformRequest(
"openai/gpt-oss-120b",
{
model: "openai/gpt-oss-120b",
reasoning_effort: "low",
messages: [],
},
false,
{}
) as Record<string, unknown>;
assert.equal(result.reasoning_effort, "low");
assert.equal(result.chat_template_kwargs, undefined);
});
test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 preserves a native thinking switch", () => {
const result = sanitizeReasoningEffortForProvider(
{
reasoning_effort: "xhigh",
chat_template_kwargs: { enable_thinking: false, custom_flag: true },
messages: [],
},
"nvidia",
"z-ai/glm-5.2",
null
) as Record<string, unknown>;
assert.equal(result.reasoning_effort, undefined);
assert.deepEqual(result.chat_template_kwargs, {
enable_thinking: false,
custom_flag: true,
});
});
test("sanitizeReasoningEffortForProvider: NVIDIA GLM-5.2 mapping is narrowly scoped", () => {
const otherModel = { reasoning_effort: "high", messages: [] };
const otherProvider = { reasoning_effort: "high", messages: [] };
assert.equal(
sanitizeReasoningEffortForProvider(otherModel, "nvidia", "z-ai/glm-5.1", null),
otherModel
);
assert.equal(
sanitizeReasoningEffortForProvider(otherProvider, "openai", "z-ai/glm-5.2", null),
otherProvider
);
});
// ── Native DeepSeek (api.deepseek.com) ───────────────────────────────────────
// DeepSeek V4 thinking mode accepts reasoning_effort ONLY as {high, max}. The
// internal OmniRoute scale (low|medium|high|xhigh, xhigh = top) must be mapped