From cd5b2df869e41ce6b5576d9f325db7cfe3e69220 Mon Sep 17 00:00:00 2001 From: thyjeff <183022254+thyjeff@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:54:12 +0530 Subject: [PATCH] fix(reasoning): unblock reasoning_content on chat completions and broaden Gemini thinking (#7750) --- src/adapters/google.ts | 8 ++++---- src/adapters/inline-think-tags.ts | 6 +++--- src/chat/inbound.ts | 2 +- tests/adapters/google/google-adapter.test.ts | 16 ++++++++------- .../chat-inbound-reasoning-none.test.ts | 20 ++++++++++++++++++- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/adapters/google.ts b/src/adapters/google.ts index 5da6e0f9441..f328706ea1c 100644 --- a/src/adapters/google.ts +++ b/src/adapters/google.ts @@ -1040,8 +1040,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte && (configuredEfforts !== undefined ? configuredEfforts.length > 0 : (provider.googleMode !== "vertex" - && (parsed.modelId === "gemini-3.5-flash" || parsed.modelId === "gemini-3.6-flash"))); - returnsThoughtSummaries = (provider.googleMode === "cloud-code-assist" || (thinkingEligible && provider.showThinkingSummary === true)) + && /^gemini-/.test(parsed.modelId))); + returnsThoughtSummaries = (provider.googleMode === "cloud-code-assist" || (thinkingEligible && provider.showThinkingSummary !== false)) && /^gemini-/.test(routedModelId) && !isImageCapableModel(parsed.modelId); // AI Studio's `-tiered` spelling is wire-only; CCA aliases may migrate to another generation. const identityModelId = provider.googleMode === "cloud-code-assist" ? routedModelId : parsed.modelId; @@ -1085,7 +1085,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte ? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning) : undefined; const includeThoughts = thinkingEligible - && provider.showThinkingSummary === true + && provider.showThinkingSummary !== false && parsed.options.hideThinkingSummary !== true && /^gemini-/.test(routedModelId); if (thinkingLevel || includeThoughts) { @@ -1139,7 +1139,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte antigravitySession = sessionId; // CCA Gemini exposes provider-authored thought summaries with includeThoughts. // Other CCA model families do not share this request contract. - const includeThoughts = provider.showThinkingSummary === true + const includeThoughts = provider.showThinkingSummary !== false && parsed.options.hideThinkingSummary !== true && /^gemini-/.test(wireModelId) && !isImageCapableModel(parsed.modelId); diff --git a/src/adapters/inline-think-tags.ts b/src/adapters/inline-think-tags.ts index a6e6e2f21a3..b229218562d 100644 --- a/src/adapters/inline-think-tags.ts +++ b/src/adapters/inline-think-tags.ts @@ -2,10 +2,10 @@ import type { AdapterEvent } from "../types"; import { modelInList } from "../types"; import type { TranslatorBudget } from "../lib/translator-budget"; -type ThinkingTag = "" | "" | ""; +type ThinkingTag = "" | "" | "" | "" | ""; type ParserState = "pre" | "thinking" | "scanning" | "streaming"; -const OPEN_TAGS: ThinkingTag[] = ["", "", ""]; +const OPEN_TAGS: ThinkingTag[] = ["", "", "", "", ""]; const MAX_OPEN_TAG = Math.max(...OPEN_TAGS.map(t => t.length)); const MAX_CLOSE_TAG = Math.max(...OPEN_TAGS.map(t => ` { expect(await thinkingConfig(optedIn, "claude-sonnet-4-6", "high")).toEqual({ thinkingLevel: "high" }); }); - test("a provider without the opt-in keeps the CCA wire unchanged", async () => { - expect(await thinkingConfig(ccaProvider, "gemini-3.8-flash", "high")).toBeUndefined(); - expect(await thinkingConfig(ccaProvider, "gemini-3.7-flash", "high")).toEqual({ thinkingLevel: "high" }); + test("a provider without the opt-in enables CCA thinkingConfig by default for eligible Gemini models", async () => { + expect(await thinkingConfig(ccaProvider, "gemini-3.8-flash", "high")).toEqual({ includeThoughts: true }); + expect(await thinkingConfig(ccaProvider, "gemini-3.7-flash", "high")).toEqual({ thinkingLevel: "high", includeThoughts: true }); + expect(await thinkingConfig({ ...ccaProvider, showThinkingSummary: false }, "gemini-3.8-flash", "high")).toBeUndefined(); + expect(await thinkingConfig({ ...ccaProvider, showThinkingSummary: false }, "gemini-3.7-flash", "high")).toEqual({ thinkingLevel: "high" }); }); test("an explicit client opt-out stops the thought text at the source", async () => { @@ -925,10 +927,10 @@ describe("google adapter — Vertex and direct Gemini thought-text opt-in", () = .toEqual({ includeThoughts: true }); }); - test("omits includeThoughts when showThinkingSummary is absent, false, or client-suppressed", async () => { + test("omits includeThoughts when showThinkingSummary is false or client-suppressed", async () => { const vertexOptIn = { ...vertexBase, showThinkingSummary: true }; expect(await wireThinkingConfig(vertexBase, "gemini-4-argon", "high")) - .toEqual({ thinkingLevel: "high" }); + .toEqual({ thinkingLevel: "high", includeThoughts: true }); expect(await wireThinkingConfig({ ...vertexBase, showThinkingSummary: false }, "gemini-4-argon", "high")) .toEqual({ thinkingLevel: "high" }); expect(await wireThinkingConfig(vertexOptIn, "gemini-4-argon", "high", true)) @@ -996,8 +998,8 @@ describe("google adapter — Vertex and direct Gemini thought-text opt-in", () = const defaultAdapter = withTestTranslatorBudget(createGoogleAdapter(vertexBase as never)); await defaultAdapter.buildRequest(makeParsed("gemini-4-argon", "high")); expect(await extractFirstEvent(defaultAdapter, stream)).toEqual({ - type: "reasoning_raw_delta", - text: "vertex summary", + type: "thinking_delta", + thinking: "vertex summary", }); const unladderedAdapter = withTestTranslatorBudget( diff --git a/tests/responses/chat-inbound-reasoning-none.test.ts b/tests/responses/chat-inbound-reasoning-none.test.ts index cc627d7784c..9bb573fcdfa 100644 --- a/tests/responses/chat-inbound-reasoning-none.test.ts +++ b/tests/responses/chat-inbound-reasoning-none.test.ts @@ -59,8 +59,26 @@ describe("F7 explicit reasoning disable survives the Chat boundary", () => { expect(reasoningOf(body)?.effort).toBeUndefined(); }); - test("omitting an effort entirely still produces no effort", () => { + test("omitting an effort entirely still produces no effort but defaults summary to auto", () => { const body = chatCompletionsToResponsesBody(chat({})); expect(reasoningOf(body)?.effort).toBeUndefined(); + expect(reasoningOf(body)?.summary).toBe("auto"); + }); + + test("honors explicit include_reasoning: false by omitting reasoning entirely", () => { + const body = chatCompletionsToResponsesBody(chat({ include_reasoning: false })); + expect(reasoningOf(body)).toBeUndefined(); + }); + + test("preserves explicit summary none", () => { + const body = chatCompletionsToResponsesBody(chat({ reasoning: { summary: "none" } })); + expect(reasoningOf(body)?.summary).toBe("none"); + }); + + test("plain chat requests validate against schema with summary auto without injecting unwanted effort", () => { + const body = chatCompletionsToResponsesBody(chat({ model: "gpt-4o" })); + expect(responsesRequestSchema.safeParse(body).success).toBe(true); + expect(reasoningOf(body)?.summary).toBe("auto"); + expect(reasoningOf(body)?.effort).toBeUndefined(); }); });