open-swe/agent/utils/github_checks.py
Johannes du Plessis bfcb714c5c
feat: PR review page (#1495)
* feat: PR review page

* feat: manual review trigger card on admin page

* feat: finding focus mode with floating card and dimmed backdrop

* fix: dim only info panel, subtle block highlight for focused finding

* refactor: move PR reviews into agents page with file-tree sidebar

* fix: address review feedback

- paginate list_reviews until enough accessible records collected
- include rename/metadata-only files in diff API with empty hunks
- remount ReviewBody on head_sha change so viewed-files state resets

* fix: review tree background + finding card anchoring

* fix: remove no-op PR size chip and dead check links

* fix: parallel diff fetch, complete reReview type, shared github_headers

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-11 16:11:10 -07:00

138 lines
4.3 KiB
Python

"""GitHub Checks API helpers for the reviewer's PR check run.
A check run named ``Open SWE Review`` is created on the PR head SHA when an
auto-review is dispatched, so the PR's checks section shows the review as
in-progress. ``publish_review`` (or the after-agent fallback) completes it.
All calls are best-effort: check runs require the GitHub App's
``Checks: Read & write`` permission, and a missing permission must never
break review dispatch or publish.
"""
from __future__ import annotations
import logging
from datetime import UTC, datetime
from typing import Literal
import httpx
logger = logging.getLogger(__name__)
REVIEW_CHECK_RUN_NAME = "Open SWE Review"
_GITHUB_API_BASE = "https://api.github.com"
CheckConclusion = Literal["success", "neutral", "failure"]
def github_headers(token: str) -> dict[str, str]:
return {
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
}
def _utc_now_iso() -> str:
return datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
async def create_review_check_run(
*,
owner: str,
repo: str,
head_sha: str,
token: str,
details_url: str | None = None,
) -> int | None:
"""Create an in-progress ``Open SWE Review`` check run on ``head_sha``.
Returns the check run id, or ``None`` on any failure (most commonly the
App lacking the Checks permission).
"""
payload: dict[str, object] = {
"name": REVIEW_CHECK_RUN_NAME,
"head_sha": head_sha,
"status": "in_progress",
"started_at": _utc_now_iso(),
"output": {
"title": "Review in progress",
"summary": "Open SWE is reviewing this pull request…",
},
}
if details_url:
payload["details_url"] = details_url
url = f"{_GITHUB_API_BASE}/repos/{owner}/{repo}/check-runs"
try:
async with httpx.AsyncClient() as client:
response = await client.post(
url, headers=github_headers(token), json=payload, timeout=30
)
response.raise_for_status()
except httpx.HTTPError:
logger.exception(
"Failed to create review check run for %s/%s@%s "
"(does the GitHub App have Checks: Read & write?)",
owner,
repo,
head_sha,
)
return None
data = response.json()
check_run_id = data.get("id") if isinstance(data, dict) else None
return check_run_id if isinstance(check_run_id, int) else None
async def complete_review_check_run(
*,
owner: str,
repo: str,
check_run_id: int,
token: str,
conclusion: CheckConclusion,
title: str,
summary: str,
) -> bool:
"""Mark a review check run as completed. Returns True on success."""
url = f"{_GITHUB_API_BASE}/repos/{owner}/{repo}/check-runs/{check_run_id}"
payload = {
"status": "completed",
"conclusion": conclusion,
"completed_at": _utc_now_iso(),
"output": {"title": title, "summary": summary},
}
try:
async with httpx.AsyncClient() as client:
response = await client.patch(
url, headers=github_headers(token), json=payload, timeout=30
)
response.raise_for_status()
except httpx.HTTPError:
logger.exception(
"Failed to complete review check run %s on %s/%s", check_run_id, owner, repo
)
return False
return True
def review_check_conclusion(surfaced_count: int) -> tuple[CheckConclusion, str, str]:
"""Map a publish result to (conclusion, title, summary).
Always ``success`` so the check is informational and non-blocking, and so
GitHub groups it under "successful checks" rather than a confusing
"neutral check". The finding count is surfaced in the title; the findings
themselves are posted as PR comments.
"""
if surfaced_count > 0:
issue_word = "issue" if surfaced_count == 1 else "issues"
return (
"success",
f"Found {surfaced_count} potential {issue_word}",
f"Open SWE surfaced {surfaced_count} potential {issue_word} on this pull request.",
)
return (
"success",
"No issues found",
"Open SWE reviewed this pull request and found no issues.",
)