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
47 changes: 36 additions & 11 deletions .github/scripts/sync_code_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
Sync code blocks in documentation files with their corresponding source files.

This script:
1. Scans MDX files for code blocks with file references (e.g., ```python expandable examples/01_standalone_sdk/02_custom_tools.py)
1. Scans MDX files for code blocks with file references (e.g., ```python expandable examples/01_standalone_sdk/02_custom_tools.py or ```yaml expandable examples/03_github_workflows/02_pr_review/workflow.yml)
2. Extracts the file path from the code block metadata
3. Reads the actual content from the source file in agent-sdk/
4. Compares the code block content with the actual file content
5. Updates the documentation if there are differences

Supported file types:
- Python files (.py) with ```python blocks
- YAML files (.yml, .yaml) with ```yaml blocks
"""

import os
Expand All @@ -26,26 +30,46 @@ def find_mdx_files(docs_path: Path) -> list[Path]:
return mdx_files


def extract_code_blocks(content: str) -> list[tuple[str, str, int, int]]:
def extract_code_blocks(content: str) -> list[tuple[str, str, str, int, int]]:
"""
Extract code blocks that reference source files.

Returns list of tuples: (file_reference, code_content, start_pos, end_pos)
Returns list of tuples: (language, file_reference, code_content, start_pos, end_pos)

Pattern matches blocks like:
```python icon="python" expandable examples/01_standalone_sdk/02_custom_tools.py
<code content>
```

OR

```yaml icon="yaml" expandable examples/03_github_workflows/02_pr_review/workflow.yml
<code content>
```
"""
# Captures ...*.py after the first line, then the body up to ```
pattern = r'```python[^\n]*\s+([^\s]+\.py)\n(.*?)```'
matches: list[tuple[str, str, int, int]] = []
for match in re.finditer(pattern, content, re.DOTALL):
matches: list[tuple[str, str, str, int, int]] = []

# Pattern for Python files
python_pattern = r'```python[^\n]*\s+([^\s]+\.py)\n(.*?)```'
for match in re.finditer(python_pattern, content, re.DOTALL):
file_ref = match.group(1)
code_content = match.group(2)
start_pos = match.start()
end_pos = match.end()
matches.append(('python', file_ref, code_content, start_pos, end_pos))

# Pattern for YAML files
yaml_pattern = r'```yaml[^\n]*\s+([^\s]+\.ya?ml)\n(.*?)```'
for match in re.finditer(yaml_pattern, content, re.DOTALL):
file_ref = match.group(1)
code_content = match.group(2)
start_pos = match.start()
end_pos = match.end()
matches.append((file_ref, code_content, start_pos, end_pos))
matches.append(('yaml', file_ref, code_content, start_pos, end_pos))

# Sort by position to maintain order
matches.sort(key=lambda x: x[3])

return matches


Expand Down Expand Up @@ -123,7 +147,7 @@ def resolve_paths() -> tuple[Path, Path]:
def update_doc_file(
doc_path: Path,
content: str,
code_blocks: list[tuple[str, str, int, int]],
code_blocks: list[tuple[str, str, str, int, int]],
agent_sdk_path: Path,
) -> bool:
"""
Expand All @@ -135,7 +159,7 @@ def update_doc_file(
new_content = content
offset = 0 # Track offset due to content changes

for file_ref, old_code, start_pos, end_pos in code_blocks:
for language, file_ref, old_code, start_pos, end_pos in code_blocks:
actual_content = read_source_file(agent_sdk_path, file_ref)
if actual_content is None:
continue
Expand All @@ -150,8 +174,9 @@ def update_doc_file(
adj_start = start_pos + offset
adj_end = end_pos + offset

# Match opening line with the appropriate language
opening_line_match = re.search(
r"```python[^\n]*\s+" + re.escape(file_ref),
r"```" + re.escape(language) + r"[^\n]*\s+" + re.escape(file_ref),
new_content[adj_start:adj_end],
)
if opening_line_match:
Expand Down
119 changes: 118 additions & 1 deletion sdk/guides/github-workflows/pr-review.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,125 @@ Automatically review pull requests, providing feedback on code quality, security
- Requesting `openhands-agent` as a reviewer
- Adding the `review-this` label to the PR

```yaml icon="yaml" expandable agent-sdk/examples/03_github_workflows/01_basic_action/workflow.yml

<Note>
The reference workflow triggers on either the "review-this" label or when the openhands-agent account is requested as a reviewer. In OpenHands organization repositories, openhands-agent has access, so this works as-is. In your own repositories, requesting openhands-agent will only work if that account is added as a collaborator or is part of a team with access. If you don't plan to grant access, use the label trigger instead, or change the condition to a reviewer handle that exists in your repo.
</Note>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct to me (right?), maybe a bit too verbose, but idk, readable.

WDYT, should we keep it? @xingyaoww @simonrosenberg

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems useful info to keep?


```yaml icon="yaml" expandable examples/03_github_workflows/02_pr_review/workflow.yml
---
# To set this up:
# 1. Copy this file to .github/workflows/pr-review.yml in your repository
# 2. Add your LLM_API_KEY to the repository secrets
# 3. Commit this file to your repository
# 4. Trigger the review by either:
# - Adding the "review-this" label to any PR, OR
# - Requesting openhands-agent as a reviewer
name: PR Review by OpenHands

on:
# Trigger when a label is added or a reviewer is requested
pull_request:
types: [labeled, review_requested]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
pr-review:
# Run when review-this label is added OR openhands-agent is requested as reviewer
if: |
github.event.label.name == 'review-this' ||
github.event.requested_reviewer.login == 'openhands-agent'
runs-on: ubuntu-latest
env:
# Configuration (modify these values as needed)
LLM_MODEL: <YOUR_LLM_MODEL>
LLM_BASE_URL: <YOUR_LLM_BASE_URL>
# PR context will be automatically provided by the agent script
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_BASE_BRANCH: ${{ github.event.pull_request.base.ref }}
PR_HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
REPO_NAME: ${{ github.repository }}
steps:
- name: Checkout agent-sdk repository
uses: actions/checkout@v4
with:
repository: OpenHands/agent-sdk
path: agent-sdk

- name: Checkout PR repository
uses: actions/checkout@v4
with:
# Fetch the full history to get the diff
fetch-depth: 0
path: pr-repo
# Check out the feature branch so agent can inspect the PR changes
ref: ${{ github.event.pull_request.head.ref }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Install GitHub CLI
run: |
# Install GitHub CLI for posting review comments
sudo apt-get update
sudo apt-get install -y gh

- name: Install OpenHands dependencies
run: |
# Install OpenHands SDK and tools from git repository
uv pip install --system "openhands-sdk @ git+https://github.com/OpenHands/agent-sdk.git@main#subdirectory=openhands-sdk"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say 1.0.0 ? 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or install from pypi

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually the example workflow in the SDK repo, not this repo.

cc @simonrosenberg - maybe we do want to point it to latest pypi wheel

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sure 😅

uv pip install --system "openhands-tools @ git+https://github.com/OpenHands/agent-sdk.git@main#subdirectory=openhands-tools"

- name: Check required configuration
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
run: |
if [ -z "$LLM_API_KEY" ]; then
echo "Error: LLM_API_KEY secret is not set."
exit 1
fi

echo "PR Number: $PR_NUMBER"
echo "PR Title: $PR_TITLE"
echo "Repository: $REPO_NAME"
echo "LLM model: $LLM_MODEL"
if [ -n "$LLM_BASE_URL" ]; then
echo "LLM base URL: $LLM_BASE_URL"
fi

- name: Run PR review
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Change to the PR repository directory so agent can analyze the code
cd pr-repo

# Run the PR review script from the agent-sdk checkout
uv run python ../agent-sdk/examples/03_github_workflows/02_pr_review/agent_script.py

- name: Upload logs as artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: openhands-pr-review-logs
path: |
*.log
output/
retention-days: 7
```

## Quick Start
Expand Down