diff --git a/src/adapters/openai-chat/reasoning-wire.ts b/src/adapters/openai-chat/reasoning-wire.ts index eb1f0afd3c9..cd92ba3bc77 100644 --- a/src/adapters/openai-chat/reasoning-wire.ts +++ b/src/adapters/openai-chat/reasoning-wire.ts @@ -44,7 +44,12 @@ export function declaresIndependentChatReasoningControls(provider: OcxProviderCo export function nativeChatUltraWireEffort(provider: OcxProviderConfig, modelId: string): string | undefined { const ladder = configuredReasoningEfforts(provider, modelId); if (ladder?.includes("ultra")) return "ultra"; - if (declaresThinkingToggleWire(provider, modelId)) return ladder ? resolveEffortAtOrBelow("max", ladder) ?? "max" : "max"; + if (declaresThinkingToggleWire(provider, modelId)) { + // Keep the original alias for the independent policy to omit effort and select thinking. + if (declaresIndependentChatReasoningEffort(provider, modelId) + && isReasoningEffortOmitted(reasoningEffortMapFor(provider, modelId)?.max)) return "max"; + return ladder ? resolveEffortAtOrBelow("max", ladder) ?? "max" : "max"; + } return mapReasoningEffort(provider, modelId, "ultra"); } diff --git a/structure/providers/chat-compat.md b/structure/providers/chat-compat.md index ad45d9b8cc9..9978e04874f 100644 --- a/structure/providers/chat-compat.md +++ b/structure/providers/chat-compat.md @@ -112,7 +112,7 @@ Passthrough and routed-compaction raw-body repairs emit it as a user turn (`exte Stateful developer-guidance injection reuses that validator for its raw insertion boundary, so parsed messages and stored raw history retain the same task/guidance order. -Native Chat passthrough and translated Chat share `src/adapters/openai-chat/reasoning-wire.ts` after provider resolution. Explicit reasoning/effort false declarations and empty strength ladders remove unsupported effort; toggle false removes the switch without declaring an unknown strength axis unsupported. A declared toggle wire serializes the independent thinking choice without treating enabled/disabled/adaptive discriminators as strengths. On a dual-control wire, an explicit `__omit__` effort alias removes the strength while the caller's effort still selects the thinking switch. Dual-control requests still pass through gateway-object projection and tool-bearing effort omission; a legacy toggle configuration gains a second serialized control only with explicit effort support; unknown declarations preserve legacy raw forwarding. Native passthrough also sanitizes routed reasoning history so `reasoning` input items do not send +Native Chat passthrough and translated Chat share `src/adapters/openai-chat/reasoning-wire.ts` after provider resolution. Explicit reasoning/effort false declarations and empty strength ladders remove unsupported effort; toggle false removes the switch without declaring an unknown strength axis unsupported. A declared toggle wire serializes the independent thinking choice without treating enabled/disabled/adaptive discriminators as strengths. On a dual-control wire, an explicit `__omit__` effort alias removes the strength while the caller's effort still selects the thinking switch. Native `ultra` honors the `max` omission alias before ladder clamping, unless the declared ladder supports native `ultra` itself. Dual-control requests still pass through gateway-object projection and tool-bearing effort omission; a legacy toggle configuration gains a second serialized control only with explicit effort support; unknown declarations preserve legacy raw forwarding. Native passthrough also sanitizes routed reasoning history so `reasoning` input items do not send non-empty `content` arrays to upstream models that reject them. Chat Completions bridging repairs orphan `toolResult` messages by inserting a synthetic assistant `tool_call` before tool messages. It also repairs the opposite direction (260718): an assistant `tool_calls` round left dangling — diff --git a/tests/adapters/openai/openai-chat-reasoning-wire-policy.test.ts b/tests/adapters/openai/openai-chat-reasoning-wire-policy.test.ts index 0a0b17c8f64..597b32f54ac 100644 --- a/tests/adapters/openai/openai-chat-reasoning-wire-policy.test.ts +++ b/tests/adapters/openai/openai-chat-reasoning-wire-policy.test.ts @@ -316,6 +316,56 @@ describe("toggle-only Chat reasoning capability", () => { } }); + test.each([ + [undefined, undefined], [undefined, false], [undefined, true], + ["gateway-object", undefined], ["gateway-object", false], ["gateway-object", true], + ] as const)("dual-axis %s omits ultra before ladder clamping with thinking override %s", (reasoningWireFormat, thinkingEnabled) => { + const target: OcxProviderConfig = { + ...toggleProvider, reasoningWireFormat, + modelCapabilities: { [toggleModel]: { supportsThinkingToggle: true, supportsReasoningEffort: true } }, + modelReasoningEfforts: { [toggleModel]: ["low", "high"] }, + modelReasoningEffortMap: { [toggleModel]: { max: "__omit__" } }, + }; + for (const effort of ["max", "ultra"]) { + for (const body of Object.values(finalToggleBodies(request(effort), target, toggleModel, thinkingEnabled))) { + expect(body.thinking).toEqual({ type: thinkingEnabled === false ? "disabled" : "enabled" }); + expect(body.reasoning_effort).toBeUndefined(); + expect(body.reasoning).toBeUndefined(); + } + } + }); + + test.each([undefined, "gateway-object"] as const)("dual-axis %s still clamps ultra when max has no omission alias", reasoningWireFormat => { + const target: OcxProviderConfig = { + ...toggleProvider, reasoningWireFormat, + modelCapabilities: { [toggleModel]: { supportsThinkingToggle: true, supportsReasoningEffort: true } }, + modelReasoningEfforts: { [toggleModel]: ["low", "high"] }, + }; + for (const thinkingEnabled of [undefined, false, true]) { + for (const body of Object.values(finalToggleBodies(request("ultra"), target, toggleModel, thinkingEnabled))) { + expect(body.thinking).toEqual({ type: thinkingEnabled === false ? "disabled" : "enabled" }); + expect(body.reasoning_effort).toBe(reasoningWireFormat ? undefined : "high"); + expect(body.reasoning).toEqual(reasoningWireFormat ? { enabled: thinkingEnabled !== false, effort: "high" } : undefined); + } + } + }); + + test.each([undefined, "gateway-object"] as const)("disabled toggle omits aliased ultra on %s", reasoningWireFormat => { + const target: OcxProviderConfig = { + ...toggleProvider, reasoningWireFormat, + modelCapabilities: { [toggleModel]: { supportsThinkingToggle: false, supportsReasoningEffort: true } }, + modelReasoningEfforts: { [toggleModel]: ["low", "high"] }, + modelReasoningEffortMap: { [toggleModel]: { max: "__omit__" } }, + }; + for (const thinkingEnabled of [undefined, false, true]) { + for (const body of Object.values(finalToggleBodies(request("ultra"), target, toggleModel, thinkingEnabled))) { + expect(body.thinking).toBeUndefined(); + expect(body.reasoning_effort).toBeUndefined(); + expect(body.reasoning).toBeUndefined(); + } + } + }); + test("native dual-axis wire preserves explicitly supported ultra despite the max omission alias", () => { const target: OcxProviderConfig = { ...toggleProvider, @@ -323,9 +373,15 @@ describe("toggle-only Chat reasoning capability", () => { modelReasoningEfforts: { [toggleModel]: ["high", "max", "ultra"] }, modelReasoningEffortMap: { [toggleModel]: { max: "__omit__" } }, }; - const body = JSON.parse(buildOpenAIChatPassthroughRequest(target, request("ultra"), toggleModel, false).body); - expect(body.reasoning_effort).toBe("ultra"); - expect(body.thinking).toEqual({ type: "enabled" }); + for (const thinkingEnabled of [undefined, false, true]) { + const bodies = finalToggleBodies(request("ultra"), target, toggleModel, thinkingEnabled); + expect(bodies.native.reasoning_effort).toBe("ultra"); + expect(bodies.translated.reasoning_effort).toBeUndefined(); + for (const body of Object.values(bodies)) { + expect(body.thinking).toEqual({ type: thinkingEnabled === false ? "disabled" : "enabled" }); + expect(body.reasoning).toBeUndefined(); + } + } }); test.each([