argus-code-review 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/CHANGELOG.md +14 -1
  2. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/PKG-INFO +1 -1
  3. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/config.py +5 -0
  4. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/gemini_runner.py +15 -1
  5. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_config.py +16 -0
  6. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_gemini_runner.py +119 -0
  7. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/.gitignore +0 -0
  8. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/LICENSE +0 -0
  9. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/README.md +0 -0
  10. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/__init__.py +0 -0
  11. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/bench.py +0 -0
  12. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/bench_default.toml +0 -0
  13. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/cli.py +0 -0
  14. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/coverage.py +0 -0
  15. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/dotenv_utils.py +0 -0
  16. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/gemini_cache.py +0 -0
  17. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/github_client.py +0 -0
  18. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/graph.py +0 -0
  19. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/helpers.py +0 -0
  20. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/llm/models.py +0 -0
  21. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/llm/output_models.py +0 -0
  22. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/llm/pricing.py +0 -0
  23. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/models.py +0 -0
  24. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/openai_client.py +0 -0
  25. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/openai_runner.py +0 -0
  26. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/pipeline_models.py +0 -0
  27. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/__init__.py +0 -0
  28. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/actions_scanner.py +0 -0
  29. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/engine.py +0 -0
  30. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/eslint_bundle/.gitignore +0 -0
  31. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/eslint_bundle/README.md +0 -0
  32. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/eslint_bundle/eslint.config.js +0 -0
  33. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/eslint_bundle/package-lock.json +0 -0
  34. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/eslint_bundle/package.json +0 -0
  35. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/js_scanner.py +0 -0
  36. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/migration_scanner.py +0 -0
  37. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/rules/README.md +0 -0
  38. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/sarif.py +0 -0
  39. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/scanner_utils.py +0 -0
  40. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/secrets_scanner.py +0 -0
  41. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/shadow.py +0 -0
  42. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/terraform_scanner.py +0 -0
  43. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/precheck/workflow_lint_scanner.py +0 -0
  44. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/__init__.py +0 -0
  45. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-blocking-validator.md +0 -0
  46. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-coverage-check.md +0 -0
  47. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-cross-cutting.md +0 -0
  48. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-feedback-verifier.md +0 -0
  49. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-lite.md +0 -0
  50. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-planner.md +0 -0
  51. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-preflight-router.md +0 -0
  52. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-prior-art.md +0 -0
  53. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-deployment.md +0 -0
  54. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-frontend.md +0 -0
  55. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-infra.md +0 -0
  56. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-llm-patterns.md +0 -0
  57. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-observability.md +0 -0
  58. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-orchestration.md +0 -0
  59. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-security.md +0 -0
  60. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-slackbot.md +0 -0
  61. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-specialist-sql.md +0 -0
  62. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-subagent.md +0 -0
  63. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-tests-and-docs.md +0 -0
  64. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts/pr-review-writer.md +0 -0
  65. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/prompts_runtime.py +0 -0
  66. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/repo_provision.py +0 -0
  67. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/review_tools.py +0 -0
  68. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/runners.py +0 -0
  69. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/__init__.py +0 -0
  70. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/http.py +0 -0
  71. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/models.py +0 -0
  72. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/precheck.py +0 -0
  73. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/resolver.py +0 -0
  74. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/session.py +0 -0
  75. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/sql.py +0 -0
  76. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/argus/storage/sqlite.py +0 -0
  77. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/pyproject.toml +0 -0
  78. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/008_add_code_reviews.sql +0 -0
  79. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/009_add_reviewer_version.sql +0 -0
  80. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/010_add_review_patterns.sql +0 -0
  81. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/011_add_review_progress_columns.sql +0 -0
  82. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/015_create_agent_runs.sql +0 -0
  83. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/016_add_agent_runs_failure_reason.sql +0 -0
  84. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/schema/017_add_precheck_rules.sql +0 -0
  85. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/__init__.py +0 -0
  86. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/conftest.py +0 -0
  87. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/golden/review_response.schema.json +0 -0
  88. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/__init__.py +0 -0
  89. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_backend_contract.py +0 -0
  90. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_http.py +0 -0
  91. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_models.py +0 -0
  92. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_resolver.py +0 -0
  93. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_session.py +0 -0
  94. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_sql.py +0 -0
  95. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/storage/test_sqlite_backend.py +0 -0
  96. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_actions_scanner.py +0 -0
  97. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_argus_review_local.py +0 -0
  98. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_bench.py +0 -0
  99. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_catchup_gate.py +0 -0
  100. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_cli_args.py +0 -0
  101. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_cli_output_contract.py +0 -0
  102. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_cli_post_review.py +0 -0
  103. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_cli_preflight.py +0 -0
  104. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_cli_prompts.py +0 -0
  105. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_conftest.py +0 -0
  106. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_gemini_cache.py +0 -0
  107. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_github_client_checks_signal.py +0 -0
  108. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_github_client_write.py +0 -0
  109. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_fetch_diff.py +0 -0
  110. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_get_llm_temperature.py +0 -0
  111. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_http_guards.py +0 -0
  112. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_precheck.py +0 -0
  113. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_preflight_image_bump.py +0 -0
  114. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_progress.py +0 -0
  115. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_storage_resolution.py +0 -0
  116. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_graph_timeout_surfacing.py +0 -0
  117. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_js_scanner.py +0 -0
  118. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_lite_review_cost.py +0 -0
  119. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_llm_models.py +0 -0
  120. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_llm_models_override.py +0 -0
  121. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_llm_pricing.py +0 -0
  122. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_migration_scanner.py +0 -0
  123. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_models.py +0 -0
  124. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_multi_round.py +0 -0
  125. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_openai_client.py +0 -0
  126. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_openai_runner.py +0 -0
  127. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_packaging.py +0 -0
  128. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_plan_review.py +0 -0
  129. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_precheck_engine.py +0 -0
  130. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_precheck_engine_integration.py +0 -0
  131. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_precheck_sarif.py +0 -0
  132. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_precheck_shadow.py +0 -0
  133. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_precheck_shadow_integration.py +0 -0
  134. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_prompts.py +0 -0
  135. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_repo_provision.py +0 -0
  136. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_review_patterns_integration.py +0 -0
  137. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_review_tools.py +0 -0
  138. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_runners_context7.py +0 -0
  139. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_runners_context_usage.py +0 -0
  140. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_runners_helpers.py +0 -0
  141. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_runners_new.py +0 -0
  142. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_scanner_utils.py +0 -0
  143. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_secrets_scanner.py +0 -0
  144. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_specialist_validation.py +0 -0
  145. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_storage_precheck.py +0 -0
  146. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_terraform_scanner.py +0 -0
  147. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_terraform_scanner_integration.py +0 -0
  148. {argus_code_review-0.2.0 → argus_code_review-0.2.1}/tests/test_workflow_lint_scanner.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-09-10
11
+
12
+ ### Added
13
+
14
+ - `GOOGLE_BASE_URL` proxy support for the Gemini runner (`argus/gemini_runner.py`),
15
+ mirroring `OPENAI_BASE_URL`: closes a gap where the Gemini platform could only
16
+ be used with a raw, unproxied `GOOGLE_API_KEY` talking directly to
17
+ `generativelanguage.googleapis.com`, unlike the OpenAI (`OPENAI_BASE_URL`) and
18
+ Claude (`ANTHROPIC_BASE_URL`) paths that the `argus-review-loop` skill relies
19
+ on to route through a short-lived, PR-scoped credential proxy instead of a
20
+ standing API key.
21
+
10
22
  ## [0.2.0] - 2026-09-10
11
23
 
12
24
  ### Added
@@ -175,7 +187,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
175
187
  packaged set.
176
188
  - `argus --version`, `argus prompts list`, and `argus prompts export`.
177
189
 
178
- [Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.2.0...HEAD
190
+ [Unreleased]: https://github.com/redesignhealth/argus-review/compare/v0.2.1...HEAD
191
+ [0.2.1]: https://github.com/redesignhealth/argus-review/compare/v0.2.0...v0.2.1
179
192
  [0.2.0]: https://github.com/redesignhealth/argus-review/compare/v0.1.5...v0.2.0
180
193
  [0.1.5]: https://github.com/redesignhealth/argus-review/compare/v0.1.4...v0.1.5
181
194
  [0.1.4]: https://github.com/redesignhealth/argus-review/compare/v0.1.3...v0.1.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: argus-code-review
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Self-orchestrated PR review agent using LangGraph + Claude Agent SDK
5
5
  Project-URL: Repository, https://github.com/redesignhealth/argus-review
6
6
  Project-URL: Issues, https://github.com/redesignhealth/argus-review/issues
@@ -135,6 +135,10 @@ class Settings(BaseSettings):
135
135
  ``platform = "gemini"``. Only required if you actually
136
136
  opt a role into that platform (see ``argus.bench``'s
137
137
  override chain) -- the packaged default bench never does.
138
+ GOOGLE_BASE_URL: Optional override for the Gemini API endpoint (e.g. for
139
+ proxying). Mirrors ``OPENAI_BASE_URL`` above. Note that GOOGLE_API_KEY
140
+ will be forwarded to whatever endpoint is configured here; point only
141
+ at trusted proxy hosts.
138
142
  ARGUS_BENCH_FILE: Highest-priority bench-config override file. See
139
143
  ``argus.bench`` for the full override search chain.
140
144
  ARGUS_NO_BENCH_OVERRIDES: Set truthy to ignore every bench override
@@ -154,6 +158,7 @@ class Settings(BaseSettings):
154
158
  OPENAI_API_KEY: str
155
159
  OPENAI_BASE_URL: str | None = None
156
160
  GOOGLE_API_KEY: str | None = None
161
+ GOOGLE_BASE_URL: str | None = None
157
162
 
158
163
  ARGUS_DB_URL: str | None = None
159
164
  SUPABASE_DB_URL: str | None = None
@@ -607,7 +607,21 @@ async def _run_turns(
607
607
  # clause catches and translates into a clean `failure_reason="timeout"` result
608
608
  # -- rather than racing the outer deadline so closely that the raw
609
609
  # httpx exception could escape uncaught instead.
610
- http_options = types.HttpOptions(timeout=int(timeout_s * _HTTP_TIMEOUT_FRACTION * 1000))
610
+ #
611
+ # `base_url` is only set when truthy; `None` safely defaults to the
612
+ # standard Gemini endpoint (`generativelanguage.googleapis.com`) --
613
+ # mirrors `OPENAI_BASE_URL`'s exact same optional-proxy pattern in
614
+ # `argus.openai_runner`/`argus.openai_client`. This same `http_options`
615
+ # instance is reused as-is by `_maybe_activate_cache`'s own
616
+ # short-lived `genai.Client` (see that function's docstring for why it
617
+ # needs a separate client instance), so threading `base_url` through
618
+ # here covers both `genai.Client(...)` construction sites in this
619
+ # module without a second read of `settings.GOOGLE_BASE_URL`.
620
+ base_url = getattr(settings, "GOOGLE_BASE_URL", None)
621
+ http_options = types.HttpOptions(
622
+ timeout=int(timeout_s * _HTTP_TIMEOUT_FRACTION * 1000),
623
+ base_url=base_url if isinstance(base_url, str) and base_url else None,
624
+ )
611
625
  client = genai.Client(api_key=api_key, http_options=http_options)
612
626
  logger.info(
613
627
  "Gemini session started: %s model=%s caching=%s timeout=%ss",
@@ -149,6 +149,7 @@ def test_optional_fields_default_to_none(monkeypatch: pytest.MonkeyPatch) -> Non
149
149
  "ARGUS_CONTEXT7_LIBRARY_ID",
150
150
  "ARGUS_CONTEXT7_BASE_URL",
151
151
  "GOOGLE_API_KEY",
152
+ "GOOGLE_BASE_URL",
152
153
  "ARGUS_BENCH_FILE",
153
154
  "ARGUS_GEMINI_CACHE_DIR",
154
155
  ):
@@ -160,6 +161,7 @@ def test_optional_fields_default_to_none(monkeypatch: pytest.MonkeyPatch) -> Non
160
161
  assert settings.ARGUS_CONTEXT7_BASE_URL is None
161
162
  assert settings.ARGUS_PROMPTS_DIR is None
162
163
  assert settings.GOOGLE_API_KEY is None
164
+ assert settings.GOOGLE_BASE_URL is None
163
165
  assert settings.ARGUS_BENCH_FILE is None
164
166
  assert settings.ARGUS_GEMINI_CACHE_DIR is None
165
167
 
@@ -252,3 +254,17 @@ def test_openai_base_url_override(monkeypatch: pytest.MonkeyPatch) -> None:
252
254
  monkeypatch.setenv("OPENAI_BASE_URL", "https://proxy.example.com/v1")
253
255
  settings = get_settings()
254
256
  assert settings.OPENAI_BASE_URL == "https://proxy.example.com/v1"
257
+
258
+
259
+ def test_google_base_url_defaults_to_none(monkeypatch: pytest.MonkeyPatch) -> None:
260
+ _set_required(monkeypatch)
261
+ monkeypatch.delenv("GOOGLE_BASE_URL", raising=False)
262
+ settings = get_settings()
263
+ assert settings.GOOGLE_BASE_URL is None
264
+
265
+
266
+ def test_google_base_url_override(monkeypatch: pytest.MonkeyPatch) -> None:
267
+ _set_required(monkeypatch)
268
+ monkeypatch.setenv("GOOGLE_BASE_URL", "https://proxy.example.com/v1beta")
269
+ settings = get_settings()
270
+ assert settings.GOOGLE_BASE_URL == "https://proxy.example.com/v1beta"
@@ -1395,3 +1395,122 @@ class TestGeminiRedactionHooks:
1395
1395
  inputs = {"label": "test"}
1396
1396
  redacted = _redact_gemini_inputs(inputs)
1397
1397
  assert redacted == {"label": "test"}
1398
+
1399
+
1400
+ # ---------------------------------------------------------------------------
1401
+ # GOOGLE_BASE_URL threading (mirrors OPENAI_BASE_URL in
1402
+ # tests/test_openai_runner.py / tests/test_openai_client.py): a proxy base
1403
+ # URL configured via settings must reach the underlying `genai.Client`'s
1404
+ # `http_options.base_url`, at every `genai.Client(...)` construction site in
1405
+ # this module -- the session's own client (`_run_turns`) AND the dedicated,
1406
+ # short-lived client `_maybe_activate_cache`'s `_create_fn` builds for
1407
+ # `caches.create()`.
1408
+ # ---------------------------------------------------------------------------
1409
+
1410
+
1411
+ class TestGoogleBaseUrlOverride:
1412
+ async def test_settings_google_base_url_reaches_session_client_http_options(self) -> None:
1413
+ """GOOGLE_BASE_URL on settings is threaded through to the session's
1414
+ own `genai.Client`'s `http_options.base_url`."""
1415
+ entry = _make_entry(caching="off")
1416
+ settings = _make_settings()
1417
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1418
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1419
+ fake_client = _make_fake_client([response])
1420
+
1421
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1422
+ result = await run_session_gemini(
1423
+ entry=entry,
1424
+ system_prompt="sys",
1425
+ user_message="msg",
1426
+ settings=settings,
1427
+ repo_root="/tmp/does-not-need-to-exist",
1428
+ )
1429
+
1430
+ assert result.failure_reason is None
1431
+ mock_ctor.assert_called_once()
1432
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1433
+ assert http_options.base_url == "https://proxy.example.com/v1beta"
1434
+
1435
+ async def test_no_google_base_url_defaults_to_sdk_default(self) -> None:
1436
+ """Without an override, `http_options.base_url` stays `None` -- the
1437
+ SDK's own default (`generativelanguage.googleapis.com`) applies,
1438
+ exactly like the OPENAI_BASE_URL/AsyncOpenAI pattern this mirrors."""
1439
+ entry = _make_entry(caching="off")
1440
+ settings = _make_settings() # GOOGLE_BASE_URL never set on this MagicMock
1441
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1442
+ fake_client = _make_fake_client([response])
1443
+
1444
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1445
+ result = await run_session_gemini(
1446
+ entry=entry,
1447
+ system_prompt="sys",
1448
+ user_message="msg",
1449
+ settings=settings,
1450
+ repo_root="/tmp/does-not-need-to-exist",
1451
+ )
1452
+
1453
+ assert result.failure_reason is None
1454
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1455
+ assert http_options.base_url is None
1456
+
1457
+ async def test_google_base_url_composes_with_existing_timeout(self) -> None:
1458
+ """A `base_url` override must compose with the existing `timeout=`
1459
+ `HttpOptions` field -- both set together on the same instance --
1460
+ rather than one replacing the other."""
1461
+ from argus.gemini_runner import _HTTP_TIMEOUT_FRACTION
1462
+
1463
+ entry = _make_entry(caching="off")
1464
+ settings = _make_settings(session_timeout=300)
1465
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1466
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1467
+ fake_client = _make_fake_client([response])
1468
+
1469
+ with patch("argus.gemini_runner.genai.Client", return_value=fake_client) as mock_ctor:
1470
+ result = await run_session_gemini(
1471
+ entry=entry,
1472
+ system_prompt="sys",
1473
+ user_message="msg",
1474
+ settings=settings,
1475
+ repo_root="/tmp/does-not-need-to-exist",
1476
+ timeout_s=10.0,
1477
+ )
1478
+
1479
+ assert result.failure_reason is None
1480
+ http_options = mock_ctor.call_args.kwargs["http_options"]
1481
+ assert http_options.base_url == "https://proxy.example.com/v1beta"
1482
+ assert http_options.timeout == int(10.0 * _HTTP_TIMEOUT_FRACTION * 1000)
1483
+
1484
+ async def test_google_base_url_also_reaches_the_cache_creation_client(self) -> None:
1485
+ """`_maybe_activate_cache`'s `_create_fn` builds its own, separate
1486
+ `genai.Client` for `caches.create()` (see that function's docstring)
1487
+ -- it must carry the same `base_url` override as the session's own
1488
+ client, not silently fall back to the real API host."""
1489
+ entry = _make_entry(caching="on")
1490
+ settings = _make_settings()
1491
+ settings.GOOGLE_BASE_URL = "https://proxy.example.com/v1beta"
1492
+ response = _make_response(calls=[("finish_review", {"files_explored": []})])
1493
+ fake_client = _make_fake_client([response], cache_name="cachedContents/abc123")
1494
+
1495
+ constructed_http_options: list[types.HttpOptions] = []
1496
+
1497
+ def _new_client(*args: Any, **kwargs: Any) -> MagicMock:
1498
+ constructed_http_options.append(kwargs["http_options"])
1499
+ return fake_client
1500
+
1501
+ with patch("argus.gemini_runner.genai.Client", side_effect=_new_client):
1502
+ result = await run_session_gemini(
1503
+ entry=entry,
1504
+ system_prompt="sys",
1505
+ user_message="msg",
1506
+ settings=settings,
1507
+ repo_root="/tmp/does-not-need-to-exist",
1508
+ )
1509
+
1510
+ assert result.failure_reason is None
1511
+ # Two `genai.Client` constructions: the session's own, and the
1512
+ # dedicated cache-creation client -- both must carry the override.
1513
+ assert len(constructed_http_options) == 2
1514
+ assert all(
1515
+ ho.base_url == "https://proxy.example.com/v1beta" for ho in constructed_http_options
1516
+ )