Dev.to AI 🤖 Ai 👁 0 📖 5 min read

Gemini 4 Hydrogen in a React chat: the migration details that actually matter

Google released Gemini 4 Hydrogen (gemini-4-hydrogen) on November 12, 2026. It's the fast, cheaper model in the Gemini 4 family, sitting below Carbon and Argon. It's on the free and paid tiers of the Gemini API and AI St

Google released Gemini 4 Hydrogen (gemini-4-hydrogen) on November 12, 2026. It's the fast, cheaper model in the Gemini 4 family, sitting below Carbon and Argon. It's on the free and paid tiers of the Gemini API and AI Studio, and the paid price is flat across the whole context window: $1.50 per 1M input tokens and $7.50 per 1M output tokens (thinking tokens count as output).

Google's launch post says that compared with Gemini 3.8 Flash it finishes tasks 2.5x faster at its default setting and more than triples Terminal-Bench 4.1 scores (57% vs. 18.2%). Those are Google's numbers. I haven't benchmarked them myself.

What I did test is the boring part: wiring Hydrogen into a streaming React chat with the Vercel AI SDK, and checking what actually goes over the wire. A few details in the migration guide are easy to miss.

What changed for developers

From the official "What's new in Gemini 4 Hydrogen" guide:

  • Four thinking levels: low, medium, high (the default) and a new xhigh. minimal is rejected with an error.
  • One unified window: input, thinking and output share 1,048,576 tokens. A single request still caps at 65,536 output tokens, streaming or not. To go longer you use background execution or client-side continuation.
  • Sampling params are gone: remove temperature, top_p and top_k. In-range values are ignored, and out-of-range values return a 400.
  • No explicit caching: cachedContents / caches.create aren't supported. Implicit caching runs automatically on prompts of at least 4,096 tokens and discounts cached input by 95%.
  • Thought signatures matter: if you manage history yourself with generateContent, send prior model turns and thought signatures back unchanged.

The guide also suggests running your evals at all four levels and picking the lowest level that meets your quality bar. That's good advice for a chat UI, where latency is most of the user experience.

SDK support

@ai-sdk/google 4.0.119 added the gemini-4-hydrogen model ID and the xhigh thinking level (see the package changelog). I used [email protected] with @ai-sdk/[email protected].

The route handler

// app/api/chat/route.ts
import { streamText, convertToModelMessages, type UIMessage } from 'ai';
import { google } from '@ai-sdk/google';

export const maxDuration = 60;

type Level = 'low' | 'medium' | 'high' | 'xhigh';

export async function POST(req: Request) {
  const { messages, level = 'medium' }: { messages: UIMessage[]; level?: Level } =
    await req.json();

  const result = streamText({
    model: google('gemini-4-hydrogen'),
    messages: await convertToModelMessages(messages),
    // No temperature/topP: Hydrogen ignores them (out-of-range values return a 400).
    providerOptions: {
      google: { thinkingConfig: { thinkingLevel: level, includeThoughts: true } },
    },
  });

  return result.toUIMessageStreamResponse({ sendReasoning: true });
}

I default to medium because Google's own table recommends it for responsive chat and interactive coding. high is the API default, though, so if you omit the level you pay for more thinking than a chat turn usually needs.

The client

A plain useChat component that shows reasoning in a collapsible block and lets the user pick the thinking level for each message:

'use client';
import { useState } from 'react';
import { useChat } from '@ai-sdk/react';

export function Chat() {
  const [input, setInput] = useState('');
  const [level, setLevel] = useState<'low' | 'medium' | 'high' | 'xhigh'>('medium');
  const { messages, sendMessage, status, stop } = useChat();

  return (
    <div className="mx-auto flex max-w-2xl flex-col gap-4 p-4">
      {messages.map((m) => (
        <div key={m.id} className={m.role === 'user' ? 'self-end' : 'self-start'}>
          {m.parts.map((part, i) =>
            part.type === 'reasoning' ? (
              <details key={i} className="text-sm text-muted-foreground">
                <summary>Thinking</summary>
                <p className="whitespace-pre-wrap">{part.text}</p>
              </details>
            ) : part.type === 'text' ? (
              <p key={i} className="whitespace-pre-wrap">{part.text}</p>
            ) : null,
          )}
        </div>
      ))}
      <form
        className="flex gap-2"
        onSubmit={(e) => {
          e.preventDefault();
          if (!input.trim()) return;
          sendMessage({ text: input }, { body: { level } });
          setInput('');
        }}
      >
        <select value={level} onChange={(e) => setLevel(e.target.value as typeof level)}>
          <option value="low">low</option>
          <option value="medium">medium</option>
          <option value="high">high</option>
          <option value="xhigh">xhigh</option>
        </select>
        <input className="flex-1 border px-2" value={input} onChange={(e) => setInput(e.target.value)} />
        {status === 'streaming' ? (
          <button type="button" onClick={stop}>Stop</button>
        ) : (
          <button type="submit" disabled={status === 'submitted'}>Send</button>
        )}
      </form>
    </div>
  );
}

Swap the raw elements for shadcn Button, Select and Collapsible if that's your stack. The data flow stays the same.

How I tested it (and what I didn't)

To be upfront: I didn't call the live API for this post. I ran the code above on Node 24 with a custom fetch that records the outgoing request and replies with a fake SSE stream. That checks the wiring, not the model's quality or speed. Here's what it showed:

  1. The request is right. It hits models/gemini-4-hydrogen:streamGenerateContent?alt=sse with thinkingConfig: { thinkingLevel, includeThoughts: true }. The UI stream comes back as reasoning-start / reasoning-delta / reasoning-end followed by text-* parts, which is exactly what the details block renders.
  2. temperature is still forwarded, with no warning. Passing temperature: 0.2 put "temperature": 0.2 in generationConfig, and result.warnings was empty. Per Google's guide the server ignores in-range values, so this won't break anything. But if your shared config sets something out of range, you'll get a 400 and the SDK won't have warned you. I'd strip sampling params for Hydrogen.
  3. thinkingLevel: 'minimal' passes through unchanged. The provider's types still allow it for older models, so a config copied from a Gemini 3 app will type-check and then fail at the API. Search your code for minimal.
  4. The portable reasoning option maps correctly. reasoning: 'xhigh' became thinkingLevel: 'xhigh' on Hydrogen. On gemini-4-argon, the SDK downgraded it to high and raised a compatibility warning, which matches the changelog note that xhigh maps only for Hydrogen.

Honest limits

  • The speed and benchmark figures above are Google's, not mine.
  • xhigh costs more and responds more slowly, and Google warns it can overthink simple prompts. Don't make it the default in a chat box.
  • Thinking tokens are billed as output at $7.50/1M, so includeThoughts plus high on every turn adds up. Log usage.outputTokenDetails.reasoningTokens, which the SDK exposes, and watch it.
  • If you rely on explicit context caching for a big system prompt, that path is gone. Check whether implicit caching (prompts of 4,096+ tokens) covers your case.

Sources

If you'd rather not hand-roll the reasoning, tool-call and message components, Cuelark UI has shadcn-style ones built for this kind of stream.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.