Skip to content
Open
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
2 changes: 2 additions & 0 deletions plugins/pr-review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ PR reviews are automatically triggered when:
| `acp-command` | Yes for `acp` mode | `''` | Command used to start the ACP server. The command must already be available in the runner environment or be runnable through a package manager. Examples: `npx -y @zed-industries/codex-acp@0.12.0`, `codex-acp`, `claude-agent-acp`, `npx -y @agentclientprotocol/claude-agent-acp`. |
| `acp-prompt-timeout` | No | `'1800'` | Timeout in seconds for one ACP prompt turn |
| `llm-base-url` | No | `''` | Custom LLM endpoint URL |
| `llm-extra-body` | No | `''` | JSON object of extra request-body fields forwarded to the LLM provider, e.g. `{"enable_thinking": false}` to turn off default thinking on Qwen models. Ignored in ACP mode. |
| `max-iterations` | No | `''` | Cap on agent iterations (model call + tool action) per review run. Bounds runaway exploration; empty uses the SDK default. Ignored in ACP mode. |
| `review-style` | No | `roasted` | **[DEPRECATED]** Previously chose between `standard` and `roasted` review styles. Now ignored — the styles have been merged into a single unified skill. |
| `require-evidence` | No | `'false'` | Require the reviewer to enforce an `Evidence` section in the PR description with end-to-end proof: screenshots/videos for frontend work, commands and runtime output for backend or scripts, and an agent conversation link when applicable. Test output alone does not qualify. |
| `use-sub-agents` | No | `'false'` | Enable sub-agent delegation for file-level reviews in `openhands` mode. The main agent acts as a coordinator that delegates per-file review work to `file_reviewer` sub-agents via the SDK TaskToolSet, then consolidates findings into a single PR review. Useful for large PRs with many changed files. **Disabled by default** due to high token costs and potential timeouts (see [#208](https://github.com/OpenHands/extensions/issues/208)). Set to `'true'` to opt in. Ignored in ACP mode. |
Expand Down
18 changes: 18 additions & 0 deletions plugins/pr-review/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ inputs:
description: LLM base URL (optional, for custom LLM endpoints)
required: false
default: ''
llm-extra-body:
description: >
JSON object of extra request-body fields forwarded to the LLM
provider (optional). Use it for provider-specific switches such as
'{"enable_thinking": false}' or '{"thinking_budget": 2048}' on
models that think by default. Ignored in ACP mode.
required: false
default: ''
max-iterations:
description: >
Maximum agent iterations (model call + tool action) for the review
run (optional). Bounds runaway exploration so the job ends with a
clear error instead of hitting the workflow timeout. Empty uses the
SDK default. Ignored in ACP mode.
required: false
default: ''
review-style:
description: "[DEPRECATED] Previously chose between 'standard' and 'roasted' review styles. These have been merged into a single code-review skill. The input is kept for backward compatibility but no longer changes behavior. Will be removed in a future version."
required: false
Expand Down Expand Up @@ -258,6 +274,8 @@ runs:
ACP_PROMPT_TIMEOUT: ${{ inputs.acp-prompt-timeout }}
LLM_MODEL: ${{ steps.select-model.outputs.selected_model }}
LLM_BASE_URL: ${{ inputs.llm-base-url }}
LLM_EXTRA_BODY: ${{ inputs.llm-extra-body }}
MAX_ITERATIONS: ${{ inputs.max-iterations }}
REVIEW_STYLE: ${{ inputs.review-style }}
REQUIRE_EVIDENCE: ${{ inputs.require-evidence }}
COLLECT_FEEDBACK: ${{ inputs.collect-feedback }}
Expand Down
32 changes: 32 additions & 0 deletions plugins/pr-review/scripts/agent_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
LLM_API_KEY: API key for the LLM (required for OpenHands agent kind)
LLM_MODEL: Language model to use (default: anthropic/claude-sonnet-4-5-20250929)
LLM_BASE_URL: Optional base URL for LLM API
LLM_EXTRA_BODY: Optional JSON object of extra request-body fields for the LLM
MAX_ITERATIONS: Optional cap on agent iterations per review run
GITHUB_TOKEN: GitHub token for API access (required)
PR_NUMBER: Pull request number (required)
PR_TITLE: Pull request title (required)
Expand Down Expand Up @@ -893,6 +895,30 @@ def validate_environment() -> dict[str, Any]:
sys.exit(1)

api_key = os.getenv("LLM_API_KEY")

extra_body: dict[str, Any] = {}
raw_extra_body = os.getenv("LLM_EXTRA_BODY", "")
if raw_extra_body:
try:
extra_body = json.loads(raw_extra_body)
except json.JSONDecodeError as exc:
logger.error(f"LLM_EXTRA_BODY is not valid JSON: {exc}")
sys.exit(1)
if not isinstance(extra_body, dict):
logger.error("LLM_EXTRA_BODY must be a JSON object")
sys.exit(1)

max_iterations: int | None = None
raw_max_iterations = os.getenv("MAX_ITERATIONS", "")
if raw_max_iterations:
try:
max_iterations = int(raw_max_iterations)
except ValueError:
logger.error("MAX_ITERATIONS must be an integer")
sys.exit(1)
if max_iterations < 1:
logger.error("MAX_ITERATIONS must be at least 1")
sys.exit(1)
if agent_kind == "openhands" and not api_key:
logger.error(
"LLM_API_KEY is required when AGENT_KIND is 'openhands'"
Expand Down Expand Up @@ -925,6 +951,8 @@ def validate_environment() -> dict[str, Any]:
"github_token": os.getenv("GITHUB_TOKEN"),
"model": os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929"),
"base_url": os.getenv("LLM_BASE_URL"),
"extra_body": extra_body,
"max_iterations": max_iterations,
"require_evidence": _get_bool_env("REQUIRE_EVIDENCE"),
"collect_feedback": _get_bool_env("COLLECT_FEEDBACK"),
"review_run_url": os.getenv("REVIEW_RUN_URL", ""),
Expand Down Expand Up @@ -1076,6 +1104,8 @@ def create_conversation(
}
if config["base_url"]:
llm_config["base_url"] = config["base_url"]
if config["extra_body"]:
llm_config["litellm_extra_body"] = config["extra_body"]

llm = LLM(**llm_config)

Expand Down Expand Up @@ -1114,6 +1144,8 @@ def create_conversation(
conversation_kwargs["visualizer"] = DelegationVisualizer(
name="PR Review Coordinator"
)
if config["max_iterations"]:
conversation_kwargs["max_iteration_per_run"] = config["max_iterations"]

return Conversation(**conversation_kwargs)

Expand Down
Loading