From 26933c2f592bda25df735c555620a2a978cfefb6 Mon Sep 17 00:00:00 2001 From: EthanGuo-coder <188665641+EthanGuo-coder@users.noreply.github.com> Date: Thu, 14 May 2026 08:03:50 -0700 Subject: [PATCH] fix(agent/gemini-cloudcode): seed delta defaults for reasoning-only stream chunks _make_stream_chunk built delta_kwargs with only `role`, so a reasoning-only chunk produced a SimpleNamespace without a `.content` attribute. Downstream consumers that read `delta.content` then raised AttributeError on Gemini 2.5 Flash, where the thinking delta arrives before any content delta. Seed `content`, `tool_calls`, `reasoning`, and `reasoning_content` as None up front, matching the pattern already used in gemini_native_adapter.py. Key-present arguments still override the defaults. Fixes #24974 References: Related open PR #24984 (luyao618) applies the same 1-line fix; this PR adds a regression test that #24984 omits Co-Authored-By: Claude --- agent/gemini_cloudcode_adapter.py | 8 +++++++- tests/agent/test_gemini_cloudcode.py | 29 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/agent/gemini_cloudcode_adapter.py b/agent/gemini_cloudcode_adapter.py index 5bc42e3aad7..222327807be 100644 --- a/agent/gemini_cloudcode_adapter.py +++ b/agent/gemini_cloudcode_adapter.py @@ -450,7 +450,13 @@ def _make_stream_chunk( finish_reason: Optional[str] = None, reasoning: str = "", ) -> _GeminiStreamChunk: - delta_kwargs: Dict[str, Any] = {"role": "assistant"} + delta_kwargs: Dict[str, Any] = { + "role": "assistant", + "content": None, + "tool_calls": None, + "reasoning": None, + "reasoning_content": None, + } if content: delta_kwargs["content"] = content if tool_call_delta is not None: diff --git a/tests/agent/test_gemini_cloudcode.py b/tests/agent/test_gemini_cloudcode.py index dc2b1b15311..480f562aa64 100644 --- a/tests/agent/test_gemini_cloudcode.py +++ b/tests/agent/test_gemini_cloudcode.py @@ -913,6 +913,35 @@ class TestTranslateStreamEvent: assert chunks[-1].choices[0].finish_reason == "tool_calls" +class TestMakeStreamChunk: + def test_reasoning_only_chunk_has_content_none(self): + from agent.gemini_cloudcode_adapter import _make_stream_chunk + + chunk = _make_stream_chunk(model="m", reasoning="think") + delta = chunk.choices[0].delta + assert delta.content is None + assert delta.reasoning == "think" + + def test_content_only_chunk_has_reasoning_none(self): + from agent.gemini_cloudcode_adapter import _make_stream_chunk + + chunk = _make_stream_chunk(model="m", content="hello") + delta = chunk.choices[0].delta + assert delta.content == "hello" + assert delta.reasoning is None + assert delta.tool_calls is None + + def test_finish_only_chunk_has_all_fields_none(self): + from agent.gemini_cloudcode_adapter import _make_stream_chunk + + chunk = _make_stream_chunk(model="m", finish_reason="stop") + delta = chunk.choices[0].delta + assert delta.content is None + assert delta.reasoning is None + assert delta.tool_calls is None + assert chunk.choices[0].finish_reason == "stop" + + class TestGeminiCloudCodeClient: def test_client_exposes_openai_interface(self): from agent.gemini_cloudcode_adapter import GeminiCloudCodeClient