From 6836adb823a4cb7aab65b5361430ff9616d9898e Mon Sep 17 00:00:00 2001 From: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:53:24 +0530 Subject: [PATCH] Python: match User-Agent header case-insensitively prepend_agent_framework_to_user_agent looked up the exact key "User-Agent" when deciding whether an incoming headers dict already carries a user agent. HTTP header names are case-insensitive, so a caller passing default_headers={"user-agent": "my-app/1.0"} (for example to OpenAI clients or the A2A agent) ended up with two User-Agent entries: their original one plus a new agent-framework one. httpx then sends both header lines, and which value a server honors is undefined. Find the existing entry case-insensitively and prepend to it in place, so exactly one User-Agent header survives. --- .../core/agent_framework/_telemetry.py | 12 +++++- .../core/tests/core/test_telemetry.py | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_telemetry.py b/python/packages/core/agent_framework/_telemetry.py index af6309e5d32..35500240bf3 100644 --- a/python/packages/core/agent_framework/_telemetry.py +++ b/python/packages/core/agent_framework/_telemetry.py @@ -212,6 +212,16 @@ def prepend_agent_framework_to_user_agent(headers: dict[str, Any] | None = None) user_agent = get_user_agent() if not headers: return {USER_AGENT_KEY: user_agent} - headers[USER_AGENT_KEY] = f"{user_agent} {headers[USER_AGENT_KEY]}" if USER_AGENT_KEY in headers else user_agent + # HTTP header names are case-insensitive; detect an existing User-Agent entry + # regardless of casing so the framework value is prepended to it instead of + # creating a second, duplicate User-Agent header. + existing_key = next( + (key for key in headers if isinstance(key, str) and key.lower() == USER_AGENT_KEY.lower()), + None, + ) + if existing_key is None: + headers[USER_AGENT_KEY] = user_agent + else: + headers[existing_key] = f"{user_agent} {headers[existing_key]}" return headers diff --git a/python/packages/core/tests/core/test_telemetry.py b/python/packages/core/tests/core/test_telemetry.py index 7d7fc77256b..e9b4e2f9cf8 100644 --- a/python/packages/core/tests/core/test_telemetry.py +++ b/python/packages/core/tests/core/test_telemetry.py @@ -343,6 +343,47 @@ def test_modifies_original_dict(): assert "User-Agent" in headers +def test_prepend_to_lowercase_user_agent(): + """Test prepending to an existing lowercase user-agent entry. + + HTTP header names are case-insensitive, so callers may pass + ``user-agent`` instead of ``User-Agent``. The framework value must be + prepended to the existing entry rather than adding a second + User-Agent header alongside it. + """ + headers = {"user-agent": "existing-agent/1.0"} + result = prepend_agent_framework_to_user_agent(headers) + + user_agent_values = [value for key, value in result.items() if key.lower() == "user-agent"] + assert len(user_agent_values) == 1 + assert user_agent_values[0].startswith("agent-framework-python/") + assert "existing-agent/1.0" in user_agent_values[0] + + +def test_prepend_to_uppercase_user_agent(): + """Test prepending to an all-caps USER-AGENT entry.""" + headers = {"USER-AGENT": "existing-agent/2.0"} + result = prepend_agent_framework_to_user_agent(headers) + + user_agent_values = [value for key, value in result.items() if key.lower() == "user-agent"] + assert len(user_agent_values) == 1 + assert user_agent_values[0].startswith("agent-framework-python/") + assert "existing-agent/2.0" in user_agent_values[0] + + +def test_no_duplicate_user_agent_after_prepend(monkeypatch): + """Test httpx sees a single User-Agent header after the prepend.""" + import httpx + + headers = {"user-agent": "existing-agent/1.0"} + result = prepend_agent_framework_to_user_agent(headers) + request = httpx.Request("GET", "https://example.com", headers=result) + raw_values = [value for name, value in request.headers.raw if name.lower() == b"user-agent"] + + assert len(raw_values) == 1 + assert raw_values[0].startswith(b"agent-framework-python/") + + # region Test _add_user_agent_prefix