mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-20 13:52:28 +03:00
AgentRouter routes GLM models through the generic DefaultExecutor, which has
no GLM-specific handling. When the connection's OpenAI-compatible alternate
format is used, a Claude-style thinking:{type:"adaptive"} field survived
stripUnsupportedParams untouched and reached AgentRouter's upstream GLM
endpoint verbatim, which 400s (thinking.type "adaptive" is not supported by
glm models; must be one of enabled, disabled).
Add a mapThinkingType mechanism to paramSupport.ts's STRIP_RULES (in addition
to the existing drop/clamp mechanisms) and scope a rule to provider
"agentrouter" + model matching /glm-/i that remaps thinking.type from
"adaptive" to "enabled", preserving any other thinking fields (e.g.
budget_tokens) — mirroring the same mapping GlmExecutor already performs for
its own provider.
238 lines
10 KiB
TypeScript
238 lines
10 KiB
TypeScript
// Unit tests for stripUnsupportedParams: per-(provider,model) strip of params
|
|
// the upstream rejects (HTTP 400). Port from 9router#7ae9fff6 (fixes #1748).
|
|
//
|
|
// Rule coverage:
|
|
// 1. claude-opus-4 series: temperature deprecated → Anthropic 400.
|
|
// 2. github + gpt-5.4: temperature unsupported.
|
|
// 3. github + Claude (except opus/sonnet 4.6): thinking + reasoning_effort rejected.
|
|
// 4. nvidia + z-ai/glm-5.2: reasoning rejected → NVIDIA 400.
|
|
// 5. volcengine + kimi-k2-5-260127: max_tokens clamped to the Ark endpoint cap
|
|
// (32768), confirmed independently against two live-endpoint reports for the
|
|
// same Volcengine Ark Kimi coding-plan endpoint (decolua/9router#2460;
|
|
// NousResearch/hermes-agent#51773; MoonshotAI/kimi-cli#1124).
|
|
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
stripUnsupportedParams,
|
|
__STRIP_RULES_FOR_TEST,
|
|
} from "../../open-sse/translator/paramSupport.ts";
|
|
|
|
test("stripUnsupportedParams: drops temperature for claude-opus-4 models (any provider)", () => {
|
|
const body = { model: "claude-opus-4-20250514", temperature: 0.7, max_tokens: 100 };
|
|
stripUnsupportedParams("anthropic", "claude-opus-4-20250514", body);
|
|
assert.equal(body.temperature, undefined, "temperature must be stripped");
|
|
assert.equal(body.max_tokens, 100, "other params must survive");
|
|
assert.equal(body.model, "claude-opus-4-20250514", "model must not be touched");
|
|
});
|
|
|
|
test("stripUnsupportedParams: drops temperature for claude-opus-4-1 (case insensitive)", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.5 };
|
|
stripUnsupportedParams("anthropic", "CLAUDE-OPUS-4-1-20250805", body);
|
|
assert.equal(body.temperature, undefined);
|
|
});
|
|
|
|
test("stripUnsupportedParams: keeps temperature for claude-sonnet-4 (only opus-4 affected)", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.7 };
|
|
stripUnsupportedParams("anthropic", "claude-sonnet-4-20250514", body);
|
|
assert.equal(body.temperature, 0.7, "sonnet-4 still accepts temperature");
|
|
});
|
|
|
|
test("stripUnsupportedParams: keeps temperature for claude-opus-3 (regression guard)", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.7 };
|
|
stripUnsupportedParams("anthropic", "claude-3-opus-20240229", body);
|
|
assert.equal(body.temperature, 0.7);
|
|
});
|
|
|
|
test("stripUnsupportedParams: github + gpt-5.4 strips temperature", () => {
|
|
const body: Record<string, unknown> = { temperature: 1, max_completion_tokens: 200 };
|
|
stripUnsupportedParams("github", "gpt-5.4", body);
|
|
assert.equal(body.temperature, undefined);
|
|
assert.equal(body.max_completion_tokens, 200);
|
|
});
|
|
|
|
test("stripUnsupportedParams: github + gpt-5 (non-5.4) keeps temperature", () => {
|
|
const body: Record<string, unknown> = { temperature: 1 };
|
|
stripUnsupportedParams("github", "gpt-5", body);
|
|
assert.equal(body.temperature, 1);
|
|
});
|
|
|
|
test("stripUnsupportedParams: codex strips temperature and top_p (Responses 400)", () => {
|
|
const body: Record<string, unknown> = {
|
|
temperature: 0.7,
|
|
top_p: 0.9,
|
|
model: "gpt-5.6-luna-max",
|
|
input: [],
|
|
};
|
|
stripUnsupportedParams("codex", "gpt-5.6-sol-xhigh", body);
|
|
assert.equal(body.temperature, undefined, "Codex /responses rejects temperature");
|
|
assert.equal(body.top_p, undefined, "Codex /responses rejects top_p");
|
|
assert.equal(body.model, "gpt-5.6-luna-max", "other params must survive");
|
|
});
|
|
|
|
test("stripUnsupportedParams: non-codex provider keeps temperature for gpt-5.6-luna-max", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.7 };
|
|
stripUnsupportedParams("openai", "gpt-5.6-luna-max", body);
|
|
assert.equal(body.temperature, 0.7, "codex sampling strip is provider-scoped");
|
|
});
|
|
|
|
test("stripUnsupportedParams: github + Claude strips thinking + reasoning_effort", () => {
|
|
const body: Record<string, unknown> = {
|
|
thinking: { type: "enabled" },
|
|
reasoning_effort: "high",
|
|
temperature: 0.5,
|
|
};
|
|
stripUnsupportedParams("github", "claude-3-5-sonnet", body);
|
|
assert.equal(body.thinking, undefined, "Copilot rejects Claude-style thinking");
|
|
assert.equal(body.reasoning_effort, undefined);
|
|
assert.equal(body.temperature, 0.5, "non-targeted params survive");
|
|
});
|
|
|
|
test("stripUnsupportedParams: github + claude opus 4.6 KEEPS thinking + reasoning_effort", () => {
|
|
const body: Record<string, unknown> = {
|
|
thinking: { type: "enabled" },
|
|
reasoning_effort: "high",
|
|
};
|
|
stripUnsupportedParams("github", "claude-opus-4.6", body);
|
|
assert.equal(body.reasoning_effort, "high", "opus-4.6 supports reasoning_effort");
|
|
// Note: thinking survives too on opus-4.6 per upstream rule.
|
|
assert.deepEqual(body.thinking, { type: "enabled" });
|
|
});
|
|
|
|
test("stripUnsupportedParams: github + claude sonnet 4.6 KEEPS reasoning_effort", () => {
|
|
const body: Record<string, unknown> = { reasoning_effort: "low" };
|
|
stripUnsupportedParams("github", "claude-sonnet-4.6", body);
|
|
assert.equal(body.reasoning_effort, "low");
|
|
});
|
|
|
|
test("stripUnsupportedParams: non-github provider + Claude does NOT strip thinking", () => {
|
|
// The github-Claude rule is provider-scoped.
|
|
const body: Record<string, unknown> = { thinking: { type: "enabled" } };
|
|
stripUnsupportedParams("anthropic", "claude-3-5-sonnet", body);
|
|
assert.deepEqual(body.thinking, { type: "enabled" });
|
|
});
|
|
|
|
test("stripUnsupportedParams: only deletes when the key is present (never adds undefined)", () => {
|
|
const body: Record<string, unknown> = { max_tokens: 50 };
|
|
stripUnsupportedParams("anthropic", "claude-opus-4", body);
|
|
// No `temperature` key should be introduced.
|
|
assert.equal("temperature" in body, false);
|
|
});
|
|
|
|
test("stripUnsupportedParams: returns the same body reference (in-place mutation)", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.7 };
|
|
const out = stripUnsupportedParams("anthropic", "claude-opus-4", body);
|
|
assert.equal(out, body);
|
|
});
|
|
|
|
test("stripUnsupportedParams: null/undefined body is a no-op", () => {
|
|
assert.equal(stripUnsupportedParams("anthropic", "claude-opus-4", null), null);
|
|
assert.equal(stripUnsupportedParams("anthropic", "claude-opus-4", undefined), undefined);
|
|
});
|
|
|
|
test("stripUnsupportedParams: missing model is a no-op", () => {
|
|
const body: Record<string, unknown> = { temperature: 0.7 };
|
|
stripUnsupportedParams("anthropic", "", body);
|
|
assert.equal(body.temperature, 0.7);
|
|
});
|
|
|
|
test("stripUnsupportedParams: drops reasoning for nvidia z-ai/glm-5.2", () => {
|
|
const body: Record<string, unknown> = {
|
|
model: "z-ai/glm-5.2",
|
|
reasoning: { effort: "high" },
|
|
temperature: 0.7,
|
|
};
|
|
stripUnsupportedParams("nvidia", "z-ai/glm-5.2", body);
|
|
assert.equal(body.reasoning, undefined, "reasoning must be stripped");
|
|
assert.equal(body.temperature, 0.7, "other params must survive");
|
|
assert.equal(body.model, "z-ai/glm-5.2", "model must not be touched");
|
|
});
|
|
|
|
test("stripUnsupportedParams: nvidia z-ai/glm-5.1 keeps reasoning (rule is 5.2-only)", () => {
|
|
const body: Record<string, unknown> = {
|
|
model: "z-ai/glm-5.1",
|
|
reasoning: { effort: "medium" },
|
|
max_tokens: 100,
|
|
};
|
|
stripUnsupportedParams("nvidia", "z-ai/glm-5.1", body);
|
|
assert.ok(body.reasoning !== undefined, "reasoning must survive for glm-5.1");
|
|
assert.equal(body.max_tokens, 100, "other params must survive");
|
|
});
|
|
|
|
test("stripUnsupportedParams: nvidia glm-5 rule is provider-scoped (no-op for other providers)", () => {
|
|
const body: Record<string, unknown> = {
|
|
model: "z-ai/glm-5.2",
|
|
reasoning: { effort: "high" },
|
|
};
|
|
stripUnsupportedParams("openai", "z-ai/glm-5.2", body);
|
|
assert.ok(body.reasoning !== undefined, "reasoning must survive for non-nvidia provider");
|
|
});
|
|
|
|
test("stripUnsupportedParams: nvidia non-glm-5 model keeps reasoning", () => {
|
|
const body: Record<string, unknown> = {
|
|
model: "deepseek-ai/deepseek-v4-pro",
|
|
reasoning: { effort: "high" },
|
|
};
|
|
stripUnsupportedParams("nvidia", "deepseek-ai/deepseek-v4-pro", body);
|
|
assert.ok(body.reasoning !== undefined, "reasoning must survive for non-glm-5 nvidia model");
|
|
});
|
|
|
|
test("STRIP_RULES is non-empty and every rule has a drop list, a clamp mechanism, or a thinking-type map", () => {
|
|
assert.ok(__STRIP_RULES_FOR_TEST.length > 0);
|
|
for (const rule of __STRIP_RULES_FOR_TEST) {
|
|
const hasDrop = Array.isArray(rule.drop) && rule.drop.length > 0;
|
|
const hasClamp = rule.clampToModelMaxOutput === true || Number.isFinite(rule.maxOutputCap);
|
|
const hasThinkingMap =
|
|
typeof rule.mapThinkingType === "object" &&
|
|
rule.mapThinkingType !== null &&
|
|
Object.keys(rule.mapThinkingType).length > 0;
|
|
assert.ok(
|
|
hasDrop || hasClamp || hasThinkingMap,
|
|
"rule must either drop params, clamp max output, or map a thinking type"
|
|
);
|
|
assert.ok(typeof rule.match === "function" || rule.match instanceof RegExp);
|
|
}
|
|
});
|
|
|
|
test("stripUnsupportedParams: volcengine kimi-k2-5-260127 clamps max_tokens above the Ark endpoint cap (32768)", () => {
|
|
const body: Record<string, unknown> = { max_tokens: 65536 };
|
|
stripUnsupportedParams("volcengine", "kimi-k2-5-260127", body);
|
|
assert.equal(body.max_tokens, 32768, "oversized max_tokens must be clamped to the Ark cap");
|
|
});
|
|
|
|
test("stripUnsupportedParams: volcengine kimi-k2-5-260127 leaves max_tokens under the cap unchanged", () => {
|
|
const body: Record<string, unknown> = { max_tokens: 8000 };
|
|
stripUnsupportedParams("volcengine", "kimi-k2-5-260127", body);
|
|
assert.equal(body.max_tokens, 8000, "max_tokens under the cap must not be modified");
|
|
});
|
|
|
|
test("stripUnsupportedParams: volcengine kimi-k2-5-260127 also clamps max_completion_tokens/max_output_tokens", () => {
|
|
const body: Record<string, unknown> = {
|
|
max_completion_tokens: 100000,
|
|
max_output_tokens: 50000,
|
|
};
|
|
stripUnsupportedParams("volcengine", "kimi-k2-5-260127", body);
|
|
assert.equal(body.max_completion_tokens, 32768);
|
|
assert.equal(body.max_output_tokens, 32768);
|
|
});
|
|
|
|
test("stripUnsupportedParams: volcengine non-kimi model (glm-4-7-251222) is NOT clamped by the kimi rule", () => {
|
|
const body: Record<string, unknown> = { max_tokens: 65536 };
|
|
stripUnsupportedParams("volcengine", "glm-4-7-251222", body);
|
|
assert.equal(
|
|
body.max_tokens,
|
|
65536,
|
|
"kimi-specific cap must not apply to other volcengine models"
|
|
);
|
|
});
|
|
|
|
test("stripUnsupportedParams: kimi rule is provider-scoped (no-op for non-volcengine providers)", () => {
|
|
const body: Record<string, unknown> = { max_tokens: 65536 };
|
|
stripUnsupportedParams("kimi", "kimi-k2-5-260127", body);
|
|
assert.equal(
|
|
body.max_tokens,
|
|
65536,
|
|
"the Ark-specific cap must not leak to other kimi-hosting providers"
|
|
);
|
|
});
|