Files
OmniRoute/tests/unit/default-reasoning-effort-6879.test.ts
Diego Rodrigues de Sa e Souza b28331307e feat: per-model default reasoning_effort + no-think none on OpenAI path (#6879) (#7631)
Validated in merge-train 2026-07-18 @ 9084b408b: 12198/12201 pass; single red = earlyStreamKeepalive timer test, confirmed load-flake (6/6 green isolated on release tip AND the merged tree; no boarded PR touches keepalive)
2026-07-18 03:07:37 -03:00

207 lines
9.4 KiB
TypeScript

/**
* #6879 — per-model default `reasoning_effort` + `no-think/` expresses "none" on the
* OpenAI path (instead of deleting the field).
*
* Ask 1: `ModelSpec.defaultReasoningEffort` is injected as `reasoning_effort` only
* when the request carries no reasoning field of any shape (`reasoning_effort`,
* `reasoning`, `thinking`); an explicit client value always wins.
*
* Ask 2: `applyNoThinkingAlias` sets `reasoning_effort:"none"` on the OpenAI path
* (instead of deleting the field), so a thinks-by-default model actually stops
* thinking rather than falling back to its provider default. The Claude/Messages
* path is unchanged (`thinking:{type:"disabled"}`). Lanes known to reject
* `reasoning_effort` still end up with the field removed via the existing
* per-lane unsupported-param strip (open-sse/translator/paramSupport.ts) — same
* end state as today's delete-only behavior, just correct on more lanes.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { applyDefaultReasoningEffort } from "../../open-sse/services/defaultReasoningEffort.ts";
import { applyNoThinkingAlias } from "../../open-sse/utils/noThinkingAlias.ts";
import { stripUnsupportedParams } from "../../open-sse/translator/paramSupport.ts";
import { MODEL_SPECS } from "../../src/shared/constants/modelSpecs.ts";
const FIXTURE_MODEL_ID = "__test_6879_default_reasoning_effort_model__";
test.before(() => {
MODEL_SPECS[FIXTURE_MODEL_ID] = { defaultReasoningEffort: "none" };
});
test.after(() => {
delete MODEL_SPECS[FIXTURE_MODEL_ID];
});
// ---------------------------------------------------------------------------
// Ask 1: applyDefaultReasoningEffort
// ---------------------------------------------------------------------------
test("applyDefaultReasoningEffort: injects the model's default when no reasoning field is present", () => {
const body = { model: FIXTURE_MODEL_ID, messages: [] };
const result = applyDefaultReasoningEffort(body, FIXTURE_MODEL_ID);
assert.equal(result.reasoning_effort, "none");
});
test("applyDefaultReasoningEffort: an explicit reasoning_effort always wins over the model default", () => {
const body = { model: FIXTURE_MODEL_ID, messages: [], reasoning_effort: "high" };
const result = applyDefaultReasoningEffort(body, FIXTURE_MODEL_ID);
assert.equal(result.reasoning_effort, "high");
});
test("applyDefaultReasoningEffort: an explicit reasoning object always wins (Responses shape)", () => {
const body = { model: FIXTURE_MODEL_ID, messages: [], reasoning: { effort: "medium" } };
const result = applyDefaultReasoningEffort(body, FIXTURE_MODEL_ID);
assert.deepEqual(result.reasoning, { effort: "medium" });
assert.equal("reasoning_effort" in result, false);
});
test("applyDefaultReasoningEffort: an explicit thinking block always wins (Claude legacy shape)", () => {
const body = { model: FIXTURE_MODEL_ID, messages: [], thinking: { type: "enabled" } };
const result = applyDefaultReasoningEffort(body, FIXTURE_MODEL_ID);
assert.deepEqual(result.thinking, { type: "enabled" });
assert.equal("reasoning_effort" in result, false);
});
test("applyDefaultReasoningEffort: no injection when the model has no configured default (regression)", () => {
const body = { model: "gpt-5.4-sol", messages: [] };
const result = applyDefaultReasoningEffort(body, "gpt-5.4-sol");
assert.equal("reasoning_effort" in result, false);
assert.equal(result, body); // same reference — no allocation when nothing to inject
});
// ---------------------------------------------------------------------------
// Ask 2: applyNoThinkingAlias on the OpenAI path expresses "none"
// ---------------------------------------------------------------------------
test("applyNoThinkingAlias: OpenAI path sets reasoning_effort:none instead of deleting the field", () => {
const body: Record<string, unknown> = {
model: "no-think/gemini/gemini-flash-lite-latest",
messages: [],
};
const result = applyNoThinkingAlias(body, { claudeFormat: false });
assert.equal(result.applied, true);
assert.equal(result.realModel, "gemini/gemini-flash-lite-latest");
assert.equal(body.model, "gemini/gemini-flash-lite-latest");
assert.equal(body.reasoning_effort, "none");
assert.equal("reasoning" in body, false);
});
test("applyNoThinkingAlias: OpenAI path overrides a client-supplied reasoning_effort with none (alias always wins)", () => {
const body: Record<string, unknown> = {
model: "no-think/gemini/gemini-flash-lite-latest",
messages: [],
reasoning_effort: "high",
};
const result = applyNoThinkingAlias(body, { claudeFormat: false });
assert.equal(result.applied, true);
assert.equal(body.reasoning_effort, "none");
});
test("applyNoThinkingAlias: Claude/Messages path is unchanged (thinking:disabled, no reasoning_effort field)", () => {
const body: Record<string, unknown> = {
model: "no-think/claude/claude-opus-4-6",
messages: [],
reasoning_effort: "high",
};
const result = applyNoThinkingAlias(body, { claudeFormat: true });
assert.equal(result.applied, true);
assert.deepEqual(body.thinking, { type: "disabled" });
assert.equal("reasoning_effort" in body, false);
assert.equal("reasoning" in body, false);
});
test("applyNoThinkingAlias: non-alias model is untouched (regression)", () => {
const body: Record<string, unknown> = { model: "gemini/gemini-flash-lite-latest", messages: [] };
const result = applyNoThinkingAlias(body, { claudeFormat: false });
assert.equal(result.applied, false);
assert.equal("reasoning_effort" in body, false);
});
// ---------------------------------------------------------------------------
// Ask 2, fallback: a lane that rejects reasoning_effort still ends up with the
// field removed, via the pre-existing per-lane unsupported-param strip — same
// end state as today's delete-only behavior, correct on more lanes.
// ---------------------------------------------------------------------------
test("a lane known to reject reasoning_effort still drops it downstream (delete-fallback preserved)", () => {
const body: Record<string, unknown> = {
model: "no-think/github/claude-3-5-sonnet",
messages: [],
};
const alias = applyNoThinkingAlias(body, { claudeFormat: false });
assert.equal(alias.applied, true);
assert.equal(body.reasoning_effort, "none"); // set by the alias itself
// github/claude (non-4.6) is a known-unsupported lane for both thinking and
// reasoning_effort (paramSupport.ts STRIP_RULES) — the field ends up removed
// before dispatch, exactly like the old delete-only behavior.
const stripped = stripUnsupportedParams("github", "claude-3-5-sonnet", body);
assert.equal("reasoning_effort" in stripped, false);
});
// ---------------------------------------------------------------------------
// #7631: the same-format /v1/responses lane (source === target === OPENAI_RESPONSES)
// skips translateRequest's hub-and-spoke translation block entirely, so a stray
// top-level `reasoning_effort` (set upstream by applyNoThinkingAlias on the OpenAI
// path, before model-format resolution knows the target lane is Responses-native)
// must still be promoted into the Responses-shaped `reasoning.effort`, or thinking
// suppression silently does not take effect on that lane.
// ---------------------------------------------------------------------------
test("7631: translateRequest promotes a stray top-level reasoning_effort into reasoning.effort on the same-format OPENAI_RESPONSES lane", async () => {
const { translateRequest } = await import("../../open-sse/translator/index.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const body: Record<string, unknown> = {
model: "gpt-5.1-codex",
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }],
reasoning_effort: "none",
};
const result = translateRequest(
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI_RESPONSES,
"gpt-5.1-codex",
body
);
assert.equal("reasoning_effort" in result, false);
assert.deepEqual(result.reasoning, { effort: "none" });
});
test("7631: same-format OPENAI_RESPONSES lane leaves an explicit reasoning object untouched (no double-promotion)", async () => {
const { translateRequest } = await import("../../open-sse/translator/index.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const body: Record<string, unknown> = {
model: "gpt-5.1-codex",
input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }],
reasoning: { effort: "high", summary: "auto" },
};
const result = translateRequest(
FORMATS.OPENAI_RESPONSES,
FORMATS.OPENAI_RESPONSES,
"gpt-5.1-codex",
body
);
assert.deepEqual(result.reasoning, { effort: "high", summary: "auto" });
assert.equal("reasoning_effort" in result, false);
});
test("7631: cross-format openai -> openai-responses promotion is unchanged (no regression)", async () => {
const { translateRequest } = await import("../../open-sse/translator/index.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const body: Record<string, unknown> = {
model: "gpt-5.1-codex",
messages: [{ role: "user", content: "hi" }],
reasoning_effort: "none",
};
const result = translateRequest(FORMATS.OPENAI, FORMATS.OPENAI_RESPONSES, "gpt-5.1-codex", body);
assert.equal("reasoning_effort" in result, false);
assert.deepEqual(result.reasoning, { effort: "none" });
});