{"repo":"NousResearch/hermes-agent","public_id":"14387","display_label":"Gemini streaming incompatibility with stream_options argument","summary":"A bug was identified where stream_options is passed to Gemini's completions API, which does not support that keyword argument. Multiple fix attempts address omitting stream_options specifically for Gemini streaming calls.","subsystem":"agent","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":54653,"merged_at":"2026-06-29T05:52:47+00:00"},"resolution":{"state":"resolved","by":"pr-54653"},"freshness":{"state":"verified","last_verified_at":"2026-06-29T05:52:47+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":14387,"pr_number":54653,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-29T05:52:47+00:00"}],"members":[{"kind":"issue","number":14387,"title":"[Bug]: 📝 Error: Completions.create() got an unexpected keyword argument 'stream_options'","url":null,"state":"closed"},{"kind":"pr","number":14392,"title":"fix(agent): omit stream_options for Gemini streaming","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"032d70214","number":0},{"kind":"pr","value":"#54653","number":54653},{"kind":"pr","value":"#14387","number":14387}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":14460,"title":"fix(agent): skip stream_options for Gemini — unsupported keyword","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14392","number":14392},{"kind":"pr","value":"#14387","number":14387}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":54653,"title":"fix(agent): omit stream_options for native Gemini streaming","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/chat_completion_helpers.py","tests/run_agent/test_streaming.py"],"fix_hunks":[{"file":"agent/chat_completion_helpers.py","hunks":["@@ -28,6 +28,7 @@\n from hermes_cli.timeouts import get_provider_request_timeout, get_provider_stale_timeout\n from hermes_constants import PARTIAL_STREAM_STUB_ID, FINISH_REASON_LENGTH\n from agent.error_classifier import FailoverReason\n+from agent.gemini_native_adapter import is_native_gemini_base_url\n from agent.model_metadata import is_local_endpoint\n from agent.message_sanitization import (\n     _sanitize_surrogates,","@@ -1911,7 +1912,6 @@ def _call_chat_completions():\n         stream_kwargs = {\n             **api_kwargs,\n             \"stream\": True,\n-            \"stream_options\": {\"include_usage\": True},\n             \"timeout\": _httpx.Timeout(\n                 connect=_conn_cap,\n                 read=_stream_read_timeout,","@@ -1919,6 +1919,14 @@ def _call_chat_completions():\n                 pool=_conn_cap,\n             ),\n         }\n+        # OpenAI's `stream_options={\"include_usage\": True}` drives usage\n+        # accounting on OpenAI-compatible endpoints (incl. the Gemini OpenAI\n+        # compat shim and aggregators like OpenRouter).  Google's *native*\n+        # Gemini REST endpoint rejects the keyword outright\n+        # (`Completions.create() got an unexpected keyword argument\n+        # 'stream_options'`), so omit it only for that endpoint.\n+        if not is_native_gemini_base_url(agent.base_url):\n+            stream_kwargs[\"stream_options\"] = {\"include_usage\": True}\n         request_client = _set_request_client(\n             agent._create_request_openai_client(\n                 reason=\"chat_completion_stream_request\","]},{"file":"tests/run_agent/test_streaming.py","hunks":["@@ -95,6 +95,99 @@ def test_text_only_response(self, mock_close, mock_create):\n         assert response.usage is not None\n         assert response.usage.completion_tokens == 3\n \n+    @patch(\"run_agent.AIAgent._create_request_openai_client\")\n+    @patch(\"run_agent.AIAgent._close_request_openai_client\")\n+    def test_native_gemini_endpoint_omits_stream_options(self, mock_close, mock_create):\n+        \"\"\"Google's native Gemini REST endpoint rejects OpenAI-only stream_options.\"\"\"\n+        from run_agent import AIAgent\n+\n+        mock_client = MagicMock()\n+        mock_client.chat.completions.create.return_value = iter([\n+            _make_stream_chunk(content=\"Paris\", finish_reason=\"stop\", model=\"gemini\"),\n+        ])\n+        mock_create.return_value = mock_client\n+\n+        agent = AIAgent(\n+            api_key=\"[redacted:secret-assignment]\",\n+            base_url=\"https://generativelanguage.googleapis.com/v1beta\",\n+            model=\"gemini-3-flash-preview\",\n+            provider=\"gemini\",\n+            quiet_mode=True,\n+            skip_context_files=True,\n+            skip_memory=True,\n+        )\n+        agent.api_mode = \"chat_completions\"\n+        agent._interrupt_requested = False\n+\n+        response = agent._interruptible_streaming_api_call({})\n+\n+        assert response.choices[0].message.content == \"Paris\"\n+        call_kwargs = mock_client.chat.completions.create.call_args.kwargs\n+        assert call_kwargs[\"stream\"] is True\n+        assert \"stream_options\" not in call_kwargs\n+\n+    @patch(\"run_agent.AIAgent._create_request_openai_client\")\n+    @patch(\"run_agent.AIAgent._close_request_openai_client\")\n+    def test_gemini_openai_compat_shim_keeps_stream_options(self, mock_close, mock_create):\n+        \"\"\"The Gemini OpenAI-compat shim (.../openai) accepts stream_options.\"\"\"\n+        from run_agent import AIAgent\n+\n+        mock_client = MagicMock()\n+        mock_client.chat.completions.create.return_value = iter([\n+            _make_stream_chunk(content=\"ok\", finish_reason=\"stop\", model=\"gemini\"),\n+            _make_empty_chunk(usage=SimpleNamespace(prompt_tokens=2, completion_tokens=1)),\n+        ])\n+        mock_create.return_value = mock_client\n+\n+        agent = AIAgent(\n+            api_key=\"[redacted:secret-assignment]\",\n+            base_url=\"https://generativelanguage.googleapis.com/v1beta/openai\",\n+            model=\"gemini-3-flash-preview\",\n+            provider=\"gemini\",\n+            quiet_mode=True,\n+            skip_context_files=True,\n+            skip_memory=True,\n+        )\n+        agent.api_mode = \"chat_completions\"\n+        agent._interrupt_requested = False\n+\n+        response = agent._interruptible_streaming_api_call({})\n+\n+        assert response.choices[0].message.content == \"ok\"\n+        call_kwargs = mock_client.chat.completions.create.call_args.kwargs\n+        assert call_kwargs[\"stream_options\"] == {\"include_usage\": True}\n+\n+    @patch(\"run_agent.AIAgent._create_request_openai_client\")\n+    @patch(\"run_agent.AIAgent._close_request_openai_client\")\n+    def test_openai_compatible_streaming_keeps_stream_options(self, mock_close, mock_create):\n+        \"\"\"OpenAI-compatible aggregators still request final usage chunks.\"\"\"\n+        from run_agent import AIAgent\n+\n+        mock_client = MagicMock()\n+        mock_client.chat.completions.create.return_value = iter([\n+            _make_stream_chunk(content=\"ok\", finish_reason=\"stop\", model=\"test-model\"),\n+            _make_empty_chunk(usage=SimpleNamespace(prompt_tokens=2, completion_tokens=1)),\n+        ])\n+        mock_create.return_value = mock_client\n+\n+        agent = AIAgent(\n+            api_key=\"[redacted:secret-assignment]\",\n+            base_url=\"https://openrouter.ai/api/v1\",\n+            model=\"test/model\",\n+            provider=\"openrouter\",\n+            quiet_mode=True,\n+            skip_context_files=True,\n+            skip_memory=True,\n+        )\n+        agent.api_mode = \"chat_completions\"\n+        agent._interrupt_requested = False\n+\n+        response = agent._interruptible_streaming_api_call({})\n+\n+        assert response.choices[0].message.content == \"ok\"\n+        call_kwargs = mock_client.chat.completions.create.call_args.kwargs\n+        assert call_kwargs[\"stream_options\"] == {\"include_usage\": True}\n+\n     @patch(\"run_agent.AIAgent._create_request_openai_client\")\n     @patch(\"run_agent.AIAgent._close_request_openai_client\")\n     def test_tool_call_response(self, mock_close, mock_create):\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}