Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/pr_af/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ def _extract_pr_number(pr_url: str) -> int | None:
return None


def _resolve_budget_caps(
max_cost_usd: float | None, max_duration_seconds: int | None
) -> tuple[float, int]:
"""Resolve the review budget caps.

When the caller does not pass an explicit value, fall back to the
``PR_AF_MAX_COST_USD`` / ``PR_AF_MAX_DURATION_SECONDS`` env vars (so the
budget can be tuned per deployment without a code change), and finally to
the historical defaults (2.0 USD / 300s) when neither is set. An explicit
argument always wins over the env var.
"""
if max_cost_usd is None:
max_cost_usd = float(os.getenv("PR_AF_MAX_COST_USD", "2.0"))
if max_duration_seconds is None:
max_duration_seconds = int(os.getenv("PR_AF_MAX_DURATION_SECONDS", "300"))
return max_cost_usd, max_duration_seconds


def _checkout_pr_branch(target_dir: str, pr_number: int) -> None:
git_env = {**os.environ, "GIT_TERMINAL_PROMPT": "0", "GIT_ASKPASS": "echo"}
# Fetch the PR head into FETCH_HEAD rather than directly into a local
Expand Down Expand Up @@ -166,8 +184,8 @@ async def review(
base_ref: str | None = None,
head_ref: str | None = None,
depth: str = "auto",
max_cost_usd: float = 2.0,
max_duration_seconds: int = 300,
max_cost_usd: float | None = None,
max_duration_seconds: int | None = None,
focus: str = "auto",
ignore_paths: list[str] | None = None,
hints: list[str] | None = None,
Expand All @@ -186,6 +204,9 @@ async def review(
f"depth={depth!r}, dry_run={dry_run!r}",
flush=True,
)
max_cost_usd, max_duration_seconds = _resolve_budget_caps(
max_cost_usd, max_duration_seconds
)
review_input = ReviewInput(
pr_url=pr_url,
diff_text=diff_text,
Expand Down
38 changes: 38 additions & 0 deletions tests/test_budget_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Tests for env-configurable review budget caps.

Maps to the validation contract for ``_resolve_budget_caps``:

* caller passes no value + env set -> env value is used
* caller passes no value + env unset -> historical defaults (2.0 USD / 300s)
* caller passes an explicit value -> it wins over the env var
"""

from __future__ import annotations

import pytest

from pr_af.app import _resolve_budget_caps


def test_env_overrides_when_no_explicit_value(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("PR_AF_MAX_DURATION_SECONDS", "1800")
monkeypatch.setenv("PR_AF_MAX_COST_USD", "5")
cost, duration = _resolve_budget_caps(None, None)
assert duration == 1800
assert cost == 5.0


def test_defaults_when_env_unset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("PR_AF_MAX_DURATION_SECONDS", raising=False)
monkeypatch.delenv("PR_AF_MAX_COST_USD", raising=False)
cost, duration = _resolve_budget_caps(None, None)
assert duration == 300
assert cost == 2.0


def test_explicit_value_wins_over_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("PR_AF_MAX_DURATION_SECONDS", "1800")
monkeypatch.setenv("PR_AF_MAX_COST_USD", "5")
cost, duration = _resolve_budget_caps(7.0, 900)
assert duration == 900
assert cost == 7.0
Loading