diff --git a/src/combos/request.ts b/src/combos/request.ts index 0f1056cdfaf..d09cc14bd3c 100644 --- a/src/combos/request.ts +++ b/src/combos/request.ts @@ -1,5 +1,5 @@ import type { OcxComboDefaultEffort, OcxComboDefaultEffortMode, OcxComboReasoningEffortMode, OcxComboTarget, OcxConfig } from "../types"; -import { isCodexReasoningEffort, resolveEffortAtOrBelow } from "../reasoning-effort"; +import { isCodexReasoningEffort, isDeclaredReasoningEffort, resolveEffortAtOrBelow } from "../reasoning-effort"; import { resolveComboId } from "./types"; const warnedUnsupportedDefaults = new Set(); @@ -54,6 +54,11 @@ function responsesInputNodeHasImage(value: unknown): boolean { return false; } +/** + * Clone a logical combo request for one concrete target and apply the combo's reasoning policy. + * Force mode overrides every valid declared caller effort only when the target ladder proves the + * configured default is representable; unknown capabilities stay conservative. + */ export function concreteComboRequestBody( body: unknown, target: Pick, @@ -79,9 +84,12 @@ export function concreteComboRequestBody( const hasEffort = reasoningRecord !== undefined && Object.prototype.hasOwnProperty.call(reasoningRecord, "effort"); const callerEffort = reasoningRecord?.effort; - const validCallerEffort = typeof callerEffort === "string" && isCodexReasoningEffort(callerEffort); + const declaredCallerEffort = typeof callerEffort === "string" && isDeclaredReasoningEffort(callerEffort); const needsDefault = reasoning === undefined || (reasoningRecord !== undefined && !hasEffort); - const shouldForce = defaultEffortMode === "force" && validCallerEffort; + // `none` and `minimal` are valid request sentinels even though they are outside the ranked + // low..ultra Codex ladder. Force mode means the combo policy wins over any valid declared + // caller effort, including those sentinels. + const shouldForce = defaultEffortMode === "force" && declaredCallerEffort; if (!needsDefault && !shouldForce) return clone; // Picker availability treats an unknown ladder as a wildcard, but runtime // injection stays fail-closed until this concrete target advertises support. diff --git a/tests/codex-integration/combos.test.ts b/tests/codex-integration/combos.test.ts index 55d6bf84f34..dd76fa1ce57 100644 --- a/tests/codex-integration/combos.test.ts +++ b/tests/codex-integration/combos.test.ts @@ -392,12 +392,20 @@ describe("combo request cloning", () => { expect(concreteComboRequestBody({ model: "combo/x" }, target, "high", undefined).reasoning).toBeUndefined(); }); - test("force mode overrides only valid caller effort and resolves independently per target", () => { + test("force mode overrides every valid declared caller effort and resolves independently per target", () => { const raw = { model: "combo/x", reasoning: { effort: "medium", summary: "concise" } }; expect(concreteComboRequestBody(raw, target, "max", ["low", "high", "max"], "strict", "force").reasoning) .toEqual({ effort: "max", summary: "concise" }); expect(concreteComboRequestBody(raw, target, "max", ["low", "high"], "strict", "force").reasoning) .toEqual({ effort: "high", summary: "concise" }); + expect(concreteComboRequestBody( + { model: "combo/x", reasoning: { effort: "none", summary: "auto" } }, + target, "high", ["minimal", "low", "medium", "high", "xhigh", "max"], "strict", "force", + ).reasoning).toEqual({ effort: "high", summary: "auto" }); + expect(concreteComboRequestBody( + { model: "combo/x", reasoning: { effort: "minimal" } }, + target, "high", ["minimal", "low", "medium", "high", "xhigh", "max"], "strict", "force", + ).reasoning).toEqual({ effort: "high", summary: "auto" }); expect(raw.reasoning).toEqual({ effort: "medium", summary: "concise" }); }); @@ -416,6 +424,11 @@ describe("combo request cloning", () => { expect(concreteComboRequestBody( { model: "combo/x", reasoning: { effort: "medium" } }, target, "max", undefined, "strict", "force", ).reasoning).toEqual({ effort: "medium" }); + // Unknown capability stays conservative even for a declared sentinel: force cannot prove + // the target accepts the configured default, so strict mode preserves the caller value. + expect(concreteComboRequestBody( + { model: "combo/x", reasoning: { effort: "none" } }, target, "high", undefined, "strict", "force", + ).reasoning).toEqual({ effort: "none" }); expect(concreteComboRequestBody( { model: "combo/x" }, target, "max", [], "strict", "force", ).reasoning).toBeUndefined();