From e9abadc8673d0b25c9a4f464b77c4c8c1b29fb8f Mon Sep 17 00:00:00 2001 From: Ed_ Date: Mon, 15 Jun 2026 14:10:43 -0400 Subject: [PATCH] fix(ai_client): extract Gemini thought=True parts and wrap in tags for parse_thinking_trace --- src/ai_client.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ai_client.py b/src/ai_client.py index 5b5edda1..8dc67f15 100644 --- a/src/ai_client.py +++ b/src/ai_client.py @@ -1536,6 +1536,26 @@ def _ensure_gemini_client() -> None: creds = _load_credentials() _gemini_client = genai.Client(api_key=creds["gemini"]["api_key"]) +def _extract_gemini_thoughts(resp: Any) -> str: + """ + Extracts concatenated thinking text from a Gemini response object's parts. + Parts with thought=True are thinking segments; parts with thought=False or unset are visible text. + The google-genai SDK filters thoughts out of resp.text, so we must scan parts directly. + Returns "" if no thoughts are present. + """ + chunks: list[str] = [] + try: + candidates = getattr(resp, "candidates", None) or [] + for cand in candidates: + content = getattr(cand, "content", None) + if content is None: continue + parts = getattr(content, "parts", None) or [] + for p in parts: + if getattr(p, "thought", False) and getattr(p, "text", None): + chunks.append(p.text) + except Exception: pass + return "".join(chunks).strip() + def _get_gemini_history_list(chat: Any | None) -> list[Any]: if not chat: return [] if hasattr(chat, "_history"): return cast(list[Any], chat._history) @@ -1782,6 +1802,9 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, _append_comms("OUT", "tool_result_send", {"results": log}) payload = f_resps res = "\n\n".join(all_text) if all_text else "(No text returned)" + thought_text = _extract_gemini_thoughts(final_resp if stream_callback else resp) + if thought_text: + res = f"\n{thought_text}\n\n\n{res}" if monitor.enabled: monitor.end_component("ai_client._send_gemini") return Result(data=res) except Exception as e: