Skip to content

Commit b6e4a7e

Browse files
author
Mateusz
committed
fix: send x-goog-api-key header instead of 'gemini' for Gemini API requests
The GeminiInitializationStrategy was setting key_name='gemini' which was used as the HTTP header name, sending 'gemini: <key>' instead of the correct 'x-goog-api-key: <key>' required by the Gemini API. This caused 403 PERMISSION_DENIED errors with message 'Method doesn't allow unregistered callers'. Updated strategy, all affected tests, and added a regression test that verifies the correct header is sent.
1 parent 502996a commit b6e4a7e

9 files changed

Lines changed: 168 additions & 35 deletions

File tree

src/connectors/strategies/gemini.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@
1010
class GeminiInitializationStrategy:
1111
"""Initialization strategy for Gemini backend connectors.
1212
13-
This strategy sets the `key_name` to "gemini" and handles the mapping
13+
This strategy sets the `key_name` to "x-goog-api-key" and handles the mapping
1414
of `api_base_url` to `gemini_api_base_url` for Gemini backend initialization.
1515
"""
1616

1717
def augment_init_config(self, init_config: dict[str, Any]) -> dict[str, Any]:
1818
"""Augment initialization configuration for Gemini backend.
1919
20-
Sets `key_name = "gemini"` and maps `api_base_url` to `gemini_api_base_url`
20+
Sets `key_name = "x-goog-api-key"` and maps `api_base_url` to `gemini_api_base_url`
2121
if present. If `gemini_api_base_url` is not present after mapping, sets
2222
the default value.
2323
2424
Args:
2525
init_config: The base initialization configuration dictionary.
2626
2727
Returns:
28-
A new dictionary with `key_name` set to "gemini", `gemini_api_base_url`
28+
A new dictionary with `key_name` set to "x-goog-api-key", `gemini_api_base_url`
2929
mapped from `api_base_url` if present, or set to default if not present,
3030
and all other values preserved.
3131
"""
3232
augmented = dict(init_config)
33-
augmented["key_name"] = "gemini"
33+
augmented["key_name"] = "x-goog-api-key"
3434

3535
# Map api_base_url to gemini_api_base_url for Gemini backend
3636
if "api_base_url" in augmented:

tests/integration/test_custom_model_parameters.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def test_gemini_top_k_parameter(
123123
await backend.initialize(
124124
api_key="test-key",
125125
gemini_api_base_url="https://generativelanguage.googleapis.com",
126-
key_name="gemini",
126+
key_name="x-goog-api-key",
127127
)
128128
request_data = sample_request_data.model_copy(update={"top_k": 40})
129129

@@ -199,7 +199,7 @@ async def test_gemini_reasoning_effort_parameter(
199199
await backend.initialize(
200200
api_key="test-key",
201201
gemini_api_base_url="https://generativelanguage.googleapis.com",
202-
key_name="gemini",
202+
key_name="x-goog-api-key",
203203
)
204204
request_data = sample_request_data.model_copy(
205205
update={"reasoning_effort": "high"}
@@ -312,7 +312,7 @@ async def test_gemini_top_p_parameter(
312312
await backend.initialize(
313313
api_key="test-key",
314314
gemini_api_base_url="https://generativelanguage.googleapis.com",
315-
key_name="gemini",
315+
key_name="x-goog-api-key",
316316
)
317317
request_data = sample_request_data.model_copy(update={"top_p": 0.6})
318318

@@ -338,7 +338,7 @@ async def test_gemini_stop_sequences_parameter(
338338
await backend.initialize(
339339
api_key="test-key",
340340
gemini_api_base_url="https://generativelanguage.googleapis.com",
341-
key_name="gemini",
341+
key_name="x-goog-api-key",
342342
)
343343
request_data = sample_request_data.model_copy(
344344
update={"stop": ["stop1", "stop2"]}
@@ -388,7 +388,7 @@ async def test_unsupported_parameter_does_not_cause_error(
388388
await backend.initialize(
389389
api_key="test-key",
390390
gemini_api_base_url="https://generativelanguage.googleapis.com",
391-
key_name="gemini",
391+
key_name="x-goog-api-key",
392392
)
393393
request_data = sample_request_data.model_copy(
394394
update={"unsupported_param": "test"}

tests/integration/test_uri_parameters_e2e.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ async def test_gemini_with_uri_sampling_parameters(
443443
backend = backend_factory.create_backend("gemini", mock_app_config)
444444
await backend.initialize(
445445
api_key="test-gemini-key",
446-
key_name="gemini",
446+
key_name="x-goog-api-key",
447447
gemini_api_base_url="https://generativelanguage.googleapis.com",
448448
)
449449

tests/unit/connectors/strategies/test_gemini.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
class TestGeminiInitializationStrategy:
1212
"""Tests for the Gemini initialization strategy."""
1313

14-
def test_strategy_sets_key_name_to_gemini(self) -> None:
15-
"""Test that strategy sets key_name to 'gemini'."""
14+
def test_strategy_sets_key_name_to_x_goog_api_key(self) -> None:
15+
"""Test that strategy sets key_name to 'x-goog-api-key'."""
1616
strategy = GeminiInitializationStrategy()
1717
config = {"api_key": "test-key", "api_base_url": "https://api.gemini.com"}
1818

1919
result = strategy.augment_init_config(config)
2020

21-
assert result["key_name"] == "gemini"
21+
assert result["key_name"] == "x-goog-api-key"
2222

2323
def test_strategy_preserves_other_config_values(self) -> None:
2424
"""Test that strategy preserves all other configuration values."""
@@ -34,7 +34,7 @@ def test_strategy_preserves_other_config_values(self) -> None:
3434

3535
assert result["api_key"] == "test-key"
3636
assert result["auth_header_name"] == "x-api-key"
37-
assert result["key_name"] == "gemini"
37+
assert result["key_name"] == "x-goog-api-key"
3838

3939
def test_strategy_maps_api_base_url_to_gemini_api_base_url(self) -> None:
4040
"""Test that strategy maps api_base_url to gemini_api_base_url when present."""
@@ -83,7 +83,7 @@ def test_strategy_returns_new_dict_does_not_mutate_input(self) -> None:
8383
assert result is not config
8484
assert "key_name" not in config
8585
assert "gemini_api_base_url" not in config
86-
assert result["key_name"] == "gemini"
86+
assert result["key_name"] == "x-goog-api-key"
8787
assert result["gemini_api_base_url"] == "https://api.gemini.com"
8888
# Original BackendFactory behavior preserves api_base_url
8989
assert result["api_base_url"] == "https://api.gemini.com"
@@ -95,7 +95,7 @@ def test_strategy_overwrites_existing_key_name(self) -> None:
9595

9696
result = strategy.augment_init_config(config)
9797

98-
assert result["key_name"] == "gemini"
98+
assert result["key_name"] == "x-goog-api-key"
9999
assert result["key_name"] != "other-value"
100100

101101
def test_strategy_handles_empty_config(self) -> None:
@@ -105,7 +105,7 @@ def test_strategy_handles_empty_config(self) -> None:
105105

106106
result = strategy.augment_init_config(config)
107107

108-
assert result["key_name"] == "gemini"
108+
assert result["key_name"] == "x-goog-api-key"
109109
assert (
110110
result["gemini_api_base_url"] == "https://generativelanguage.googleapis.com"
111111
)
@@ -120,7 +120,7 @@ def test_strategy_handles_nested_config_values(self) -> None:
120120

121121
result = strategy.augment_init_config(config)
122122

123-
assert result["key_name"] == "gemini"
123+
assert result["key_name"] == "x-goog-api-key"
124124
assert result["api_key"] == "test-key"
125125
assert result["extra"] == config["extra"]
126126
assert result["extra"]["nested"] == "value"
@@ -139,7 +139,7 @@ def test_strategy_is_registered_with_registry(self) -> None:
139139
config = {"api_key": "test-key"}
140140
result = strategy.augment_init_config(config)
141141

142-
assert result["key_name"] == "gemini"
142+
assert result["key_name"] == "x-goog-api-key"
143143

144144
def test_strategy_registry_returns_gemini_strategy(self) -> None:
145145
"""Test that registry returns Gemini strategy for 'gemini' connector type."""
@@ -150,6 +150,6 @@ def test_strategy_registry_returns_gemini_strategy(self) -> None:
150150
config = {"api_key": "test-key", "some_other_field": "value"}
151151
result = strategy.augment_init_config(config)
152152

153-
assert result["key_name"] == "gemini"
153+
assert result["key_name"] == "x-goog-api-key"
154154
assert result["api_key"] == "test-key"
155155
assert result["some_other_field"] == "value"

tests/unit/connectors/strategies/test_registry.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def test_importing_registry_auto_discovers_strategies(self) -> None:
337337

338338
gemini_result = gemini_strategy.augment_init_config(test_config.copy())
339339
assert "key_name" in gemini_result
340-
assert gemini_result["key_name"] == "gemini"
340+
assert gemini_result["key_name"] == "x-goog-api-key"
341341

342342
anthropic_result = anthropic_strategy.augment_init_config(test_config.copy())
343343
assert "key_name" in anthropic_result
@@ -384,7 +384,7 @@ def test_lazy_auto_discovery_works_on_first_access(self) -> None:
384384
test_config = {"api_key": "test-key"}
385385
gemini_result = gemini_strategy.augment_init_config(test_config.copy())
386386
assert "key_name" in gemini_result
387-
assert gemini_result["key_name"] == "gemini"
387+
assert gemini_result["key_name"] == "x-goog-api-key"
388388

389389
anthropic_strategy = registry.get_strategy("anthropic")
390390
anthropic_result = anthropic_strategy.augment_init_config(test_config.copy())
@@ -474,7 +474,7 @@ def get_gemini_strategy(thread_id: int) -> None:
474474
"key_name" in result
475475
), f"Thread {thread_id} got default strategy instead of gemini strategy"
476476
assert (
477-
result["key_name"] == "gemini"
477+
result["key_name"] == "x-goog-api-key"
478478
), f"Thread {thread_id} got wrong strategy: {result.get('key_name')}"
479479

480480
results.append(result)
@@ -513,11 +513,11 @@ def get_gemini_strategy(thread_id: int) -> None:
513513
f"Some threads may have timed out or failed."
514514
)
515515

516-
# Verify all results are correct (all should have key_name="gemini")
516+
# Verify all results are correct (all should have key_name="x-goog-api-key")
517517
for i, result in enumerate(results):
518518
assert "key_name" in result, f"Result {i} missing key_name: {result}"
519519
assert (
520-
result["key_name"] == "gemini"
520+
result["key_name"] == "x-goog-api-key"
521521
), f"Result {i} has wrong key_name: {result.get('key_name')}"
522522

523523
# Verify discovery flag is set

tests/unit/connectors/test_provider_boundary_capture.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def handler(request: httpx.Request) -> httpx.Response:
280280
translation_service=TranslationService(),
281281
)
282282
connector.api_key = "test-gemini-key"
283-
connector.key_name = "gemini"
283+
connector.key_name = "x-goog-api-key"
284284
connector.gemini_api_base_url = "https://generativelanguage.googleapis.com"
285285

286286
envelope = await connector.chat_completions(

tests/unit/core/test_backend_factory_ensure_backend.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ async def test_ensure_backend_zai_coding_plan_keeps_explicit_key_over_live_env(
280280
"src.core.services.backend_factory.get_env_value_with_windows_persistent_fallback",
281281
return_value=("live-env-key", "process"),
282282
),
283-
patch.dict(os.environ, {"ZAI_CODING_PLAN_API_KEY": "live-env-key"}, clear=False),
283+
patch.dict(
284+
os.environ, {"ZAI_CODING_PLAN_API_KEY": "live-env-key"}, clear=False
285+
),
284286
):
285287
result = await factory.ensure_backend(backend_type, app_config, backend_config)
286288

@@ -315,7 +317,9 @@ async def test_ensure_backend_zai_coding_plan_keeps_explicit_key_over_windows_pe
315317
"src.core.services.backend_factory.get_env_value_with_windows_persistent_fallback",
316318
return_value=("windows-persistent-key", "windows-user"),
317319
),
318-
patch.dict(os.environ, {"ZAI_CODING_PLAN_API_KEY": "stale-process-key"}, clear=False),
320+
patch.dict(
321+
os.environ, {"ZAI_CODING_PLAN_API_KEY": "stale-process-key"}, clear=False
322+
),
319323
):
320324
result = await factory.ensure_backend(backend_type, app_config, backend_config)
321325

@@ -385,7 +389,9 @@ async def test_ensure_backend_zai_coding_plan_uses_live_env_when_key_missing(
385389
"src.core.services.backend_factory.get_env_value_with_windows_persistent_fallback",
386390
return_value=("live-env-key", "process"),
387391
),
388-
patch.dict(os.environ, {"ZAI_CODING_PLAN_API_KEY": "live-env-key"}, clear=False),
392+
patch.dict(
393+
os.environ, {"ZAI_CODING_PLAN_API_KEY": "live-env-key"}, clear=False
394+
),
389395
):
390396
result = await factory.ensure_backend(backend_type, app_config, backend_config)
391397

@@ -487,7 +493,7 @@ async def test_ensure_backend_gemini_specific(factory: BackendFactory) -> None:
487493
mock_init.assert_called_once()
488494
init_config = mock_init.call_args[0][1]
489495
assert init_config["api_key"] == "gemini-key"
490-
assert init_config["key_name"] == "gemini"
496+
assert init_config["key_name"] == "x-goog-api-key"
491497
assert (
492498
init_config["gemini_api_base_url"]
493499
== "https://generativelanguage.googleapis.com"

tests/unit/core/test_backend_factory_strategy_regression.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@ async def test_gemini_strategy_is_used_real_registry(
134134
# Verify Gemini strategy augmentation
135135
assert init_config["api_key"] == "test-gemini-key"
136136
assert (
137-
init_config["key_name"] == "gemini"
138-
), "Gemini strategy should set key_name='gemini'"
137+
init_config["key_name"] == "x-goog-api-key"
138+
), "Gemini strategy should set key_name='x-goog-api-key'"
139139
assert (
140140
"gemini_api_base_url" in init_config
141141
), "Gemini strategy should set gemini_api_base_url"
@@ -352,9 +352,9 @@ async def test_backward_compatibility_gemini_config_equivalence(
352352

353353
# Verify Gemini strategy preserves existing behavior
354354
assert init_config["api_key"] == "gemini-api-key"
355-
assert init_config["key_name"] == "gemini", (
356-
"Gemini strategy should set key_name='gemini' "
357-
"(preserving pre-refactoring behavior)"
355+
assert init_config["key_name"] == "x-goog-api-key", (
356+
"Gemini strategy should set key_name='x-goog-api-key' "
357+
"(using correct Gemini API header name)"
358358
)
359359
# Verify api_base_url is mapped to gemini_api_base_url
360360
assert init_config["api_base_url"] == "https://custom-gemini-api.example.com"

0 commit comments

Comments
 (0)