feat: apply reasoning effort and surface thinking blocks for Google chat models - #28273
Conversation
…king level applyReasoningEffort had no case for the google provider, so a chat's selected reasoning effort was silently dropped for Gemini models. With only include_thoughts configured, Gemini 3.7 Flash never thinks (reasoning_tokens is 0 on every step), so no reasoning parts stream, persist, or render. Map the global effort scale onto thinking_level (clamped to MINIMAL..HIGH), preserving any explicitly configured thinking_budget since fantasy rejects requests carrying both.
Admins can pin a Gemini 3+ thinking level via provider_options.google.thinking_config.thinking_level (minimal, low, medium, high). The pinned level applies when the model config offers no reasoning effort selection; a resolved per-turn effort overrides it, and thinking_budget remains mutually exclusive per the Google API contract, enforced at config validation.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 925b20ed23
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…dels Gemini 2.5 and older reject requests carrying thinking_level, so applying it for every Google model would turn a configured reasoning effort into a hard generation failure on those models. Parse the major version from the model ID and keep dropping the effort for pre-3 Gemini, non-Gemini, and unrecognized model IDs.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64c2cd5073
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…Gemini-3 models The config conversion path copied a pinned thinking_level for every Google model, so an admin pinning a level on a Gemini 2.5 config would fail every generation. Gate conversion on the same model capability check as the reasoning-effort path. Gating at call time rather than config save time also covers updates that switch a config's model without resubmitting options.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 123dbdc9c1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… model's supported set Gemini models accept different thinking_level subsets: 3 Pro launched with LOW and HIGH only, 3.1 Pro added MEDIUM, the Flash family accepts all four, and image models accept only HIGH (plus MINIMAL for flash). Replace the boolean Gemini 3+ gate with a per-model supported set and round-up clamping so an effort or pinned level maps to the nearest level with at least the requested depth instead of a rejected request. Versionless -latest aliases are recognized as current-generation models; unknown Gemini 3+ variants fall back to LOW and HIGH, the intersection of every documented non-image set.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…e openai-compat path Google AI providers route through aibridge as openai-compat clients, so the native thinking_level path never runs for chat generation. Google's OpenAI-compatible endpoint translates reasoning_effort into the model's thinking configuration but validates instead of clamping, so efforts outside the model's supported set (none/minimal/xhigh/max on Gemini 3.1 Pro, verified live) fail the whole request with HTTP 400. Clamp the effort into each Gemini model's supported set before dispatch, reusing the thinking-level tables from the native path.
Google's OpenAI-compatible endpoint never emits thought text for reasoning_effort requests: thoughts require google.thinking_config with include_thoughts, which is mutually exclusive with reasoning_effort, and the thought deltas come back inline in content marked by extra_content.google.thought plus <thought> markers rather than in the reasoning_content field OpenAI-compatible clients parse. Chats on Gemini models therefore showed no thinking blocks at all (verified live in UAT round 3). Translate both directions at the existing Gemini transport seam: - chatd's OpenAI-compat request patch swaps reasoning_effort for an equivalent extra_body.google.thinking_config (thinking_level on Gemini 3+, thinking_budget before that) with include_thoughts enabled, and rewrites responses so thought output surfaces as reasoning_content, which fantasy already converts into reasoning parts. Streaming bodies are rewritten per SSE line to preserve latency. - aibridge's chat-completions interception preserves the extra_body passthrough that openai-go's typed params drop, forwarding it to Google upstreams only. Verified live against generativelanguage.googleapis.com on gemini-3-flash-preview and gemini-2.5-flash: reasoning deltas stream through the full chatprovider stack with markers stripped and answer text intact.
…pat patch overview
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba90c731c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rite on model capability Pre-2.5 and unrecognized Gemini variants have no thinking support, so sending thinking_config could reject previously working requests. Restrict the rewrite to models with a known thinking_level set or the Gemini 2.5 thinking_budget family and leave other Gemini requests untouched.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 79751f00a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… the 2.5 chat families Specialized Gemini 2.5 variants such as image and TTS models reject thinking_config outright, so a version-only predicate would break previously working requests for them. Recognize only the Pro, Flash, and Flash-Lite family name shapes and fail closed on unknown tokens.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0862514684
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ig on the AI Bridge route Google models backed by an AI Provider route through the OpenAI-compatible client, which ignores the fantasygoogle options key, so an administrator's pinned thinking_level or thinking_budget was silently dropped. Translate the pinned config into the compat request's extra_body and merge it in the transport patch with native-path precedence: a pinned budget wins over the per-turn effort, the effort overrides a pinned level, and an explicitly disabled include_thoughts is preserved.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48ded662d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b98252d603
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… Gemini's thought metadata An answer that legitimately begins with the <thought> marker text was reclassified as reasoning and truncated. Real thought output always carries message.extra_content.google.thought, which the streaming path already requires, so demand it before splitting non-streaming content.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cea28de27a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…sponses from the raw upstream body The typed openai.ChatCompletion round trip drops provider-specific fields, so a blocking Gemini response with thought output lost its extra_content thought metadata while keeping the <thought> markers inline, leaking them to clients as answer text. Serialize the final blocking response from the raw body for Google upstreams, mirroring marshalChunk on the streaming path, with the same ID and usage overrides.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 831676f14a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review
|
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Documentation CheckUpdates Needed
Note The API reference ( Automated review via Coder Agents |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 831676f14a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
pawbana
left a comment
There was a problem hiding this comment.
Changes to aibridge LGTM.
… Gemini 3.7+ Flash gemini-3.7-flash rejects thinking_level MINIMAL with HTTP 400, so the default reasoning effort (none) failed every generation. Versions 3 through 3.6 of the flash and flash-lite families still accept it (live-verified against the Google API). The versionless latest aliases and unknown future flash versions also stay off MINIMAL because gemini-flash-latest already tracks 3.7 and returns the same 400, while clamping up to LOW is accepted by every flash release.
wat? |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…3 variants Codex found that the thinking-level map classified every Gemini 3+ model containing the flash token as thinking-capable, so specialized variants like gemini-3.1-flash-live-preview and gemini-3.1-flash-tts-preview got thinking_config injected on the compat path and rejected the request. Live-verified: the tts variant returns 400 (thinking level not supported), the live variant does not serve generateContent, and gemini-omni-flash-preview only supports the Interactions API. Gate the Gemini 3+ set to the Pro and Flash chat families with the same unknown-token fail-closed rule as googleSupportsThinkingBudget. Also address human review feedback: assert the rewritten completion keeps the bridge-side usage override, and join read and close errors with errors.Join instead of a hand-rolled firstError helper.
…el internal package Review feedback: avoid a top-level internal package. Mechanical git mv plus import path rewrites, no code changes.
|
@codex review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: daa2a3a8d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…and budget mutual exclusion Codex asked for interaction coverage of the new Google thinking_level selector's conflicts_with wiring. The story selects a level and asserts the budget input is disabled, clears it, sets a budget, and asserts the level select is disabled, covering both directions of the exclusion.
|
@codex review
|
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Problem
Selecting a reasoning effort on a Google (Gemini) chat model either got dropped or broke the request outright, and even when the request succeeded, Gemini chats never showed thinking blocks:
fantasygooglepath had no case inapplyReasoningEffortat all, so effort was silently ignored. With onlyinclude_thoughtsconfigured, Gemini 3+ models default to no thinking, so chats produced zero reasoning tokens.reasoning_effort. Google's OpenAI-compatible endpoint validates that value against the model's supported thinking levels instead of clamping, so out-of-range efforts fail the whole request with HTTP 400. Verified against the live Gemini API:gemini-3.1-pro-previewaccepts low/medium/high and rejects none/minimal/xhigh/max. Because the config's default effort is sent whenever the user selects nothing, a config whose default falls outside the model's supported set failed every no-selection message.reasoning_effortrequests. Thoughts requireextra_body.google.thinking_configwithinclude_thoughts, which is mutually exclusive withreasoning_effort, and even then the thought deltas come back inline incontent(markedextra_content.google.thought, wrapped in<thought>markers) rather than in thereasoning_contentfield OpenAI-compatible clients parse. So no reasoning parts were ever produced, persisted, or rendered for Gemini chats (verified live in UAT).Fix
Reasoning effort:
thinking_levelinapplyReasoningEffort, gated to Gemini 3+ models and clamped to each model's supported subset (3.0 Pro launched with LOW/HIGH only, 3.1 Pro added MEDIUM, Flash accepts all four through 3.6 while 3.7 and the latest flash aliases drop MINIMAL, image models accept HIGH plus MINIMAL for flash). Pre-Gemini-3 models rejectthinking_level, so effort degrades to a no-op for them and a config-pinned level is dropped at call time instead of failing every generation.thinking_leveltoChatModelGoogleThinkingConfigso admins can pin a level for configs without user-selectable effort. A resolved per-turn effort overrides the pinned level; an explicitthinking_budgetwins over both since the Google API rejects requests that setthinking_budgetandthinking_leveltogether, and config validation now rejects that combination up front. The admin panel picks up the new field automatically through the generated model-options schema.Thinking blocks on the openai-compat path (the path chat generation uses), translated in both directions at the existing Gemini transport seam:
reasoning_effortfor an equivalentextra_body.google.thinking_configwithinclude_thoughtsenabled:thinking_levelon Gemini 3+ (clamped to the model's supported subset),thinking_budgeton earlier models (effortnonemaps to budget 0, which disables thinking). Non-Gemini models and non-Google routes are untouched.reasoning_content, which fantasy already converts into reasoning parts: streaming bodies are rewritten per SSE line (preserving streaming latency), JSON bodies in place, with the<thought>/</thought>markers stripped at the transitions.extra_bodypassthrough that openai-go's typed params silently drop, forwarding it to Google upstreams only, so the chatd -> aibridge -> Google route carries the thinking config end to end.Validation
go test ./coderd/x/chatd/... ./aibridge/... ./internal/googleopenai ./codersdkpass; golangci-lint clean on touched packages.extra_bodypreservation (including proof the typed round trip drops it).generativelanguage.googleapis.comongemini-3-flash-previewandgemini-2.5-flashthrough the real chatprovider stack: reasoning deltas stream through with markers stripped and answer text intact.gemini-3.1-pro-preview,gemini-3-flash-preview, andgemini-2.5-flashat effort=high; no<thought>marker leaks; effort=none produces no block and no 400; multi-turn history replay works with a thinking block on each turn.make genregeneratedtypesGenerated.tsandchatModelOptionsGenerated.json;pnpm -C site lint:typespasses.