Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/reasoning-effort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] =
const CODEX_REASONING_ORDER = CODEX_REASONING_LEVELS.map(l => l.effort);
const CODEX_REASONING_SET = new Set(CODEX_REASONING_ORDER);

/**
* Sentinel wire value in reasoningEffortMap / modelReasoningEffortMap to explicitly
* omit the reasoning_effort field from the upstream wire request (issue #2356).
*/
export const REASONING_EFFORT_OMIT_SENTINEL = "__omit__";

export function isReasoningEffortOmitted(wireEffort: string | undefined): boolean {
return wireEffort === REASONING_EFFORT_OMIT_SENTINEL;
}

/** True when `effort` is a member of the Codex reasoning ladder (low..ultra). */
export function isCodexReasoningEffort(effort: string): boolean {
return CODEX_REASONING_SET.has(effort);
Expand Down Expand Up @@ -170,14 +180,21 @@ export function mapReasoningEffort(provider: OcxProviderConfig, modelId: string,
const boundary = requested === "ultra" ? "max" : requested;

const wireMap = reasoningEffortMapFor(provider, modelId);
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, boundary)) return wireMap[boundary];
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, boundary)) {
const mapped = wireMap[boundary];
return mapped === REASONING_EFFORT_OMIT_SENTINEL ? undefined : mapped;
}

const supported = configuredReasoningEfforts(provider, modelId);
const codexEffort = supported !== undefined ? clampToSupportedCodexEffort(boundary, supported) : requestToCodexEffort(boundary);
if (!codexEffort) return undefined;

// Belt for the odd config where the supported ladder is ultra-only and the clamp lands on it.
const wire = codexEffort === "ultra" ? "max" : codexEffort;
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, wire)) return wireMap[wire];
if (wireMap && Object.prototype.hasOwnProperty.call(wireMap, wire)) {
const mapped = wireMap[wire];
return mapped === REASONING_EFFORT_OMIT_SENTINEL ? undefined : mapped;
}
if (wire === REASONING_EFFORT_OMIT_SENTINEL) return undefined;
return wire;
}
62 changes: 62 additions & 0 deletions tests/reasoning-effort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,4 +973,66 @@ describe("stale reasoning-ladder self-heal", () => {
};
expect(configuredReasoningEfforts(prov, "model")).toEqual([]);
});

test("per-effort omission sentinel (__omit__) drops reasoning_effort from the wire (#2356)", () => {
const ollamaProv: OcxProviderConfig = {
adapter: "openai-chat",
baseUrl: "http://localhost:11434/v1",
modelReasoningEfforts: {
"qwen3.8-uncensored:27b-q4": ["low", "medium", "high", "xhigh", "max"],
},
modelReasoningEffortMap: {
"qwen3.8-uncensored:27b-q4": {
low: "low",
medium: "medium",
high: "__omit__",
xhigh: "__omit__",
max: "__omit__",
},
},
};

// High/xhigh/max/ultra map to undefined (omitted on wire)
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "high")).toBeUndefined();
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "xhigh")).toBeUndefined();
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "max")).toBeUndefined();
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "ultra")).toBeUndefined();

// Low and medium map to explicit wire values
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "low")).toBe("low");
expect(mapReasoningEffort(ollamaProv, "qwen3.8-uncensored:27b-q4", "medium")).toBe("medium");
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const fallbackProv: OcxProviderConfig = {
...ollamaProv,
modelReasoningEfforts: {
"qwen3.8-uncensored:27b-q4": ["low", "high"],
},
modelReasoningEffortMap: {
"qwen3.8-uncensored:27b-q4": { high: "__omit__" },
},
};
expect(mapReasoningEffort(fallbackProv, "qwen3.8-uncensored:27b-q4", "xhigh")).toBeUndefined();

// Verify in openai-chat adapter buildRequest: field is completely omitted when mapped to __omit__
const adapter = createOpenAIChatAdapter(ollamaProv);
const reqMax = adapter.buildRequest({
modelId: "qwen3.8-uncensored:27b-q4",
stream: false,
context: { messages: [{ role: "user", content: "deep thinking" }] },
options: { reasoning: "max" },
} as OcxParsedRequest);
const bodyMax = JSON.parse(reqMax.body as string);
expect(bodyMax.reasoning_effort).toBeUndefined();
expect(bodyMax).not.toHaveProperty("reasoning_effort");

// Field is present when mapped to a real string
const reqLow = adapter.buildRequest({
modelId: "qwen3.8-uncensored:27b-q4",
stream: false,
context: { messages: [{ role: "user", content: "fast turn" }] },
options: { reasoning: "low" },
} as OcxParsedRequest);
const bodyLow = JSON.parse(reqLow.body as string);
expect(bodyLow.reasoning_effort).toBe("low");
});
});
Loading