feat: add client header to GoogleChat (#4884)

Added header per integration
[guidelines](https://ai.google.dev/gemini-api/docs/partner-integration).

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Set the `x-goog-api-client` header for Google Chat requests to meet
Google partner integration guidelines. It identifies the client as
`browser-use/{version}` with a fallback of `unknown`.

- **New Features**
  - Add `x-goog-api-client` header with value `browser-use/{version}`.
- Normalize and merge `http_options`; always set/override the header in
client params.

- **Bug Fixes**
- Handle both `types.HttpOptions` and `types.HttpOptionsDict`,
preserving `timeout` and existing headers.
- Add tests covering `None`, Pydantic, and dict `http_options` to ensure
the header is set and prefixed with `browser-use/`.

<sup>Written for commit 115199d2ba.
Summary will update on new commits. <a
href="https://cubic.dev/pr/browser-use/browser-use/pull/4884?utm_source=github">Review
in cubic</a></sup>

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Saurav Panda
2026-05-22 09:59:21 -07:00
committed by GitHub
2 changed files with 85 additions and 1 deletions
+29 -1
View File
@@ -1,4 +1,5 @@
import asyncio
import importlib.metadata
import json
import logging
import random
@@ -122,6 +123,33 @@ class ChatGoogle(BaseChatModel):
"""Get logger for this chat instance"""
return logging.getLogger(f'browser_use.llm.google.{self.model}')
def _get_http_options(self) -> dict[str, Any]:
"""Get http options with the default headers set."""
try:
bu_version = importlib.metadata.version('browser-use')
except importlib.metadata.PackageNotFoundError:
bu_version = 'unknown'
header_value = f'browser-use/{bu_version}'
http_opts: dict[str, Any] = {}
if self.http_options is not None:
if isinstance(self.http_options, types.HttpOptions):
http_opts = self.http_options.model_dump(exclude_unset=True)
elif isinstance(self.http_options, dict):
http_opts = dict(self.http_options)
headers: dict[str, str] = {}
existing_headers = http_opts.get('headers')
if isinstance(existing_headers, dict):
headers = {str(k): str(v) for k, v in existing_headers.items()}
headers['x-goog-api-client'] = header_value
http_opts['headers'] = headers
return http_opts
def _get_client_params(self) -> dict[str, Any]:
"""Prepare client parameters dictionary."""
# Define base client params
@@ -131,7 +159,7 @@ class ChatGoogle(BaseChatModel):
'credentials': self.credentials,
'project': self.project,
'location': self.location,
'http_options': self.http_options,
'http_options': self._get_http_options(),
}
# Create client_params dict with non-None values
+56
View File
@@ -13,3 +13,59 @@ async def test_google_gemini_flash_latest(httpserver):
extra_kwargs={},
httpserver=httpserver,
)
def test_x_goog_api_client_header_is_set():
"""Test that the x-goog-api-client header is correctly set in the HTTP options."""
chat = ChatGoogle(model='gemini-flash-latest', api_key='fake')
# Generate the params used for genai.Client
params = chat._get_client_params()
# Extract the header
http_options = params.get('http_options', {})
headers = http_options.get('headers', {})
assert 'x-goog-api-client' in headers, 'x-goog-api-client header missing'
assert 'browser-use/' in headers['x-goog-api-client'], 'browser-use not found in x-goog-api-client header'
def test_x_goog_api_client_header_with_none_http_options():
"""Test setting header when http_options is None."""
chat = ChatGoogle(model='gemini-flash-latest', api_key='fake', http_options=None)
params = chat._get_client_params()
http_opts = params.get('http_options', {})
assert http_opts.get('headers', {}).get('x-goog-api-client', '').startswith('browser-use/')
def test_x_goog_api_client_header_with_pydantic_http_options():
"""Test setting header when http_options is a types.HttpOptions Pydantic model."""
from google.genai import types
pydantic_opts = types.HttpOptions(timeout=30, headers={'custom-header': 'value'})
chat = ChatGoogle(model='gemini-flash-latest', api_key='fake', http_options=pydantic_opts)
params = chat._get_client_params()
http_opts = params.get('http_options', {})
# Verify it extracts and preserves timeout and custom-header
assert http_opts.get('timeout') == 30
assert http_opts.get('headers', {}).get('custom-header') == 'value'
assert http_opts.get('headers', {}).get('x-goog-api-client', '').startswith('browser-use/')
def test_x_goog_api_client_header_with_dict_http_options():
"""Test setting header when http_options is a dictionary (types.HttpOptionsDict)."""
from google.genai import types
dict_opts: types.HttpOptionsDict = {
'timeout': 45,
'headers': {'another-header': 'another-value'},
}
chat = ChatGoogle(model='gemini-flash-latest', api_key='fake', http_options=dict_opts)
params = chat._get_client_params()
http_opts = params.get('http_options', {})
# Verify it preserves dictionary values and appends the tracking header
assert http_opts.get('timeout') == 45
assert http_opts.get('headers', {}).get('another-header') == 'another-value'
assert http_opts.get('headers', {}).get('x-goog-api-client', '').startswith('browser-use/')