First MVP

This commit is contained in:
Space-Banane
2026-05-22 19:25:57 +02:00
parent 673f70b32a
commit 860ccb731d
40 changed files with 2336 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
from __future__ import annotations
import json
import subprocess
import uuid
from pathlib import Path
from typing import Any
from gitea_codex_bot.config import Settings
from gitea_codex_bot.services.gitea import GiteaClient
from gitea_codex_bot.services.reviewer import normalize_review_result, prepare_review_prompt, run_review_for_pr
from gitea_codex_bot.types import ParsedCommand
def run_review_ephemeral(
settings: Settings,
*,
repo: str,
pr_number: int,
command: ParsedCommand,
) -> dict[str, Any]:
gitea = GiteaClient(settings)
prompt, _diff_context, _repo_cfg = prepare_review_prompt(settings, gitea, repo, pr_number, command)
container_name = f"codex-review-{uuid.uuid4().hex[:12]}"
install_and_run = (
"set -euo pipefail; "
"npm install -g @openai/codex >/tmp/codex-install.log 2>&1; "
"codex exec --json -m gpt-5"
)
cmd = [
"docker",
"run",
"--rm",
"-i",
"--name",
container_name,
"-e",
"OPENAI_API_KEY",
"-e",
"OPENAI_ORG_ID",
"-e",
"OPENAI_PROJECT_ID",
"-e",
"CODEX_DISABLE_TELEMETRY=1",
settings.review_runner_image,
"bash",
"-lc",
install_and_run,
]
try:
completed = subprocess.run(
cmd,
input=prompt,
text=True,
check=True,
capture_output=True,
timeout=settings.max_review_minutes * 60,
)
parsed = _parse_codex_exec_stdout(completed.stdout)
return normalize_review_result(parsed)
except Exception:
result, _repo_cfg = run_review_for_pr(settings, gitea, repo, pr_number, command)
return result
def ensure_workdir(path: str) -> Path:
target = Path(path)
target.mkdir(parents=True, exist_ok=True)
return target
def _parse_codex_exec_stdout(stdout: str) -> dict[str, Any]:
last_text: str | None = None
for line in stdout.splitlines():
line = line.strip()
if not line:
continue
try:
payload = json.loads(line)
except json.JSONDecodeError:
continue
if isinstance(payload, dict) and {"verdict", "summary", "findings"}.issubset(payload.keys()):
return payload
extracted = _extract_text(payload)
if extracted:
last_text = extracted
if not last_text:
raise RuntimeError("codex exec output did not include parseable JSON text")
return json.loads(last_text)
def _extract_text(payload: Any) -> str | None:
if isinstance(payload, str):
return payload
if isinstance(payload, dict):
for key in ("text", "message", "content", "output"):
value = payload.get(key)
text = _extract_text(value)
if text:
return text
for value in payload.values():
text = _extract_text(value)
if text:
return text
if isinstance(payload, list):
for item in payload:
text = _extract_text(item)
if text:
return text
return None