Compare commits

...

2 Commits

Author SHA1 Message Date
openhands
e79cc6517e Remove outdated create_conversation_from_files function and common tail functionality
- Removed create_conversation_from_files method that referenced non-existent common_tail.j2
- Removed CLI functionality that was unused by workflow templates
- Removed argparse and Path imports that are no longer needed
- Workflow templates continue to work with create_conversation method
- Addresses feedback from @enyst about outdated GPT-5 era functionality
2025-09-10 12:42:41 +00:00
openhands
5c6bf5f7a0 feat: add GitHub Actions workflow templates for OpenHands API
- Add OpenHands API Python helper script for automation
- Create 4 workflow templates:
  - Code review on PRs
  - Bug fix on labeled issues
  - Documentation updates (scheduled/manual)
  - Custom tasks (manual with description input)
- Include metadata files for GitHub template discovery
- Add comprehensive README with setup and usage instructions

Templates make it easy to integrate OpenHands AI agent into any GitHub repository
by simply adding OPENHANDS_API_KEY secret and selecting from template gallery.

Co-authored-by: openhands <openhands@all-hands.dev>
2025-09-05 01:50:32 +00:00
10 changed files with 630 additions and 0 deletions

123
.github/workflow-templates/README.md vendored Normal file
View File

@@ -0,0 +1,123 @@
# OpenHands GitHub Actions Workflow Templates
This directory contains workflow templates that make it easy to integrate OpenHands AI agent into your GitHub CI/CD workflows.
## Available Templates
### 1. OpenHands Code Review (`openhands-code-review.yml`)
Automatically reviews pull requests for code quality, security, and best practices.
**Triggers:** Pull request opened or updated
**Permissions:** `contents: read`, `pull-requests: write`, `issues: write`
### 2. OpenHands Bug Fix (`openhands-bug-fix.yml`)
Automatically investigates and fixes bugs when issues are labeled with 'bug'.
**Triggers:** Issue labeled with 'bug'
**Permissions:** `contents: write`, `pull-requests: write`, `issues: write`
### 3. OpenHands Documentation (`openhands-documentation.yml`)
Keeps project documentation up-to-date by reviewing code changes and updating docs.
**Triggers:** Weekly schedule, manual dispatch, or code changes
**Permissions:** `contents: write`, `pull-requests: write`
### 4. OpenHands Custom Task (`openhands-custom-task.yml`)
Run any custom development task with a manual trigger and custom description.
**Triggers:** Manual dispatch with task description input
**Permissions:** `contents: write`, `pull-requests: write`, `issues: write`
## Setup Instructions
### Prerequisites
1. **OpenHands API Key**: Get your API key from [OpenHands](https://app.all-hands.dev)
2. **GitHub Repository**: The templates work with any GitHub repository
### Installation
1. **Add API Key Secret**:
- Go to your repository's Settings → Secrets and variables → Actions
- Add a new repository secret named `OPENHANDS_API_KEY`
- Set the value to your OpenHands API key
2. **Use Templates**:
- Go to your repository's Actions tab
- Click "New workflow"
- Look for "OpenHands" templates in the template gallery
- Choose the template that fits your needs
- Customize as needed and commit
### Customization
All templates can be customized:
- **Prompts**: Modify the task descriptions to fit your specific needs
- **Triggers**: Change when workflows run (schedule, events, manual)
- **Timeouts**: Adjust polling timeouts based on task complexity
- **Permissions**: Modify based on what actions OpenHands needs to perform
### Example Customizations
#### Custom Review Criteria
```yaml
prompt = '''Please review this pull request focusing on:
- Performance optimization opportunities
- Database query efficiency
- API design consistency
- Error handling completeness
'''
```
#### Specific Documentation Updates
```yaml
prompt = '''Update the following documentation:
1. API reference in docs/api.md
2. Installation guide in README.md
3. Code examples in docs/examples/
'''
```
## How It Works
1. **Workflow Trigger**: GitHub event triggers the workflow
2. **Setup**: Installs Python and downloads the OpenHands API helper
3. **API Call**: Creates a conversation with OpenHands using your prompt
4. **Execution**: OpenHands performs the requested task in your repository
5. **Results**: OpenHands may create PRs, comments, or other outputs
## Security Considerations
- **API Key**: Keep your `OPENHANDS_API_KEY` secret secure
- **Permissions**: Templates request minimal required permissions
- **Repository Access**: OpenHands will have access to your repository during task execution
- **Review Changes**: Always review any PRs or changes made by OpenHands
## Troubleshooting
### Common Issues
1. **Missing API Key**: Ensure `OPENHANDS_API_KEY` is set in repository secrets
2. **Permission Errors**: Check that workflow permissions match template requirements
3. **Timeout Issues**: Increase timeout values for complex tasks
4. **Rate Limits**: OpenHands API has rate limits; space out workflow runs if needed
### Getting Help
- Check the [OpenHands Documentation](https://docs.all-hands.dev)
- Review workflow run logs for detailed error messages
- Ensure your repository is accessible and has the necessary permissions
## Contributing
To improve these templates:
1. Test changes thoroughly
2. Update documentation
3. Follow GitHub Actions best practices
4. Consider security implications
## License
These templates are provided under the same license as the OpenHands project.

View File

@@ -0,0 +1,23 @@
{
"name": "OpenHands Bug Fix",
"description": "Automatically investigate and fix bugs using OpenHands AI agent. Triggered when issues are labeled with 'bug'.",
"iconName": "octicon bug",
"categories": [
"Bug Fix",
"AI",
"Automation",
"Debugging"
],
"filePatterns": [
".*\\.py$",
".*\\.js$",
".*\\.ts$",
".*\\.jsx$",
".*\\.tsx$",
".*\\.java$",
".*\\.go$",
".*\\.rs$",
".*\\.cpp$",
".*\\.c$"
]
}

View File

@@ -0,0 +1,76 @@
name: OpenHands Bug Fix
on:
issues:
types: [labeled]
jobs:
openhands-fix:
if: contains(github.event.label.name, 'bug')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install OpenHands API helper
run: |
curl -O https://raw.githubusercontent.com/All-Hands-AI/OpenHands/main/scripts/openhands_api.py
python -m pip install --upgrade pip
pip install requests
- name: Run OpenHands Bug Fix
env:
OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -c "
import sys
sys.path.append('.')
from openhands_api import OpenHandsAPI
# Create bug fix prompt
prompt = '''Please investigate and fix the bug described in this GitHub issue:
Repository: ${{ github.repository }}
Issue: #${{ github.event.issue.number }}
Title: ${{ github.event.issue.title }}
Issue Description:
${{ github.event.issue.body }}
Please:
1. Analyze the issue and identify the root cause
2. Implement a fix with proper error handling
3. Add or update tests to prevent regression
4. Create a pull request with your changes
5. Include a clear description of what was fixed and how
Make sure to follow the project'\''s coding standards and best practices.
'''
# Start OpenHands conversation
client = OpenHandsAPI()
response = client.create_conversation(
initial_user_msg=prompt,
repository='${{ github.repository }}'
)
print(f'Started OpenHands bug fix: {response.get(\"conversation_id\", \"Unknown\")}')
# Poll for completion (optional - remove if you want fire-and-forget)
try:
final_response = client.poll_until_stopped(response['conversation_id'], timeout=1200)
print(f'Bug fix completed with status: {final_response.get(\"status\", \"Unknown\")}')
except Exception as e:
print(f'Bug fix may still be running: {e}')
"

View File

@@ -0,0 +1,25 @@
{
"name": "OpenHands Code Review",
"description": "Automated code review using OpenHands AI agent. Reviews PRs for code quality, security, and best practices.",
"iconName": "octicon code-review",
"categories": [
"Code Quality",
"AI",
"Automation",
"Python",
"JavaScript",
"TypeScript"
],
"filePatterns": [
".*\\.py$",
".*\\.js$",
".*\\.ts$",
".*\\.jsx$",
".*\\.tsx$",
".*\\.java$",
".*\\.go$",
".*\\.rs$",
".*\\.cpp$",
".*\\.c$"
]
}

View File

@@ -0,0 +1,71 @@
name: OpenHands Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
openhands-review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install OpenHands API helper
run: |
curl -O https://raw.githubusercontent.com/All-Hands-AI/OpenHands/main/scripts/openhands_api.py
python -m pip install --upgrade pip
pip install requests
- name: Run OpenHands Code Review
env:
OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -c "
import sys
sys.path.append('.')
from openhands_api import OpenHandsAPI
# Create review prompt
prompt = '''Please review this pull request for:
- Code quality and best practices
- Security vulnerabilities
- Performance considerations
- Documentation completeness
- Test coverage
Repository: ${{ github.repository }}
PR: #${{ github.event.number }}
Title: ${{ github.event.pull_request.title }}
Please provide constructive feedback and suggestions for improvement.
If you find any issues, please create a detailed comment explaining the problem and suggesting solutions.
'''
# Start OpenHands conversation
client = OpenHandsAPI()
response = client.create_conversation(
initial_user_msg=prompt,
repository='${{ github.repository }}'
)
print(f'Started OpenHands review: {response.get(\"conversation_id\", \"Unknown\")}')
# Poll for completion (optional - remove if you want fire-and-forget)
try:
final_response = client.poll_until_stopped(response['conversation_id'], timeout=600)
print(f'Review completed with status: {final_response.get(\"status\", \"Unknown\")}')
except Exception as e:
print(f'Review may still be running: {e}')
"

View File

@@ -0,0 +1,14 @@
{
"name": "OpenHands Custom Task",
"description": "Run any custom development task using OpenHands AI agent. Manually triggered with custom task description.",
"iconName": "octicon tools",
"categories": [
"AI",
"Automation",
"Custom",
"Development"
],
"filePatterns": [
".*"
]
}

View File

@@ -0,0 +1,78 @@
name: OpenHands Custom Task
on:
workflow_dispatch:
inputs:
task_description:
description: 'Describe the task you want OpenHands to perform'
required: true
type: string
timeout_minutes:
description: 'Timeout in minutes (default: 20)'
required: false
default: '20'
type: string
jobs:
openhands-task:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install OpenHands API helper
run: |
curl -O https://raw.githubusercontent.com/All-Hands-AI/OpenHands/main/scripts/openhands_api.py
python -m pip install --upgrade pip
pip install requests
- name: Run OpenHands Custom Task
env:
OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -c "
import sys
sys.path.append('.')
from openhands_api import OpenHandsAPI
# Create custom task prompt
prompt = '''${{ github.event.inputs.task_description }}
Repository: ${{ github.repository }}
Please complete this task following best practices:
- Write clean, well-documented code
- Add appropriate tests if needed
- Follow the project'\''s coding standards
- Create a pull request if changes are made
- Provide clear explanations of what was done
'''
# Start OpenHands conversation
client = OpenHandsAPI()
response = client.create_conversation(
initial_user_msg=prompt,
repository='${{ github.repository }}'
)
print(f'Started OpenHands custom task: {response.get(\"conversation_id\", \"Unknown\")}')
# Poll for completion
timeout_seconds = int('${{ github.event.inputs.timeout_minutes }}') * 60
try:
final_response = client.poll_until_stopped(response['conversation_id'], timeout=timeout_seconds)
print(f'Custom task completed with status: {final_response.get(\"status\", \"Unknown\")}')
except Exception as e:
print(f'Custom task may still be running or timed out: {e}')
"

View File

@@ -0,0 +1,19 @@
{
"name": "OpenHands Documentation",
"description": "Automatically update and maintain project documentation using OpenHands AI agent. Runs weekly and on code changes.",
"iconName": "octicon book",
"categories": [
"Documentation",
"AI",
"Automation",
"Maintenance"
],
"filePatterns": [
"README.md$",
".*\\.md$",
"docs/.*",
".*\\.py$",
".*\\.js$",
".*\\.ts$"
]
}

View File

@@ -0,0 +1,81 @@
name: OpenHands Documentation
on:
schedule:
- cron: '0 2 * * 1' # Weekly on Monday at 2 AM UTC
workflow_dispatch:
push:
branches: [ $default-branch ]
paths:
- 'src/**'
- 'lib/**'
- '*.py'
- '*.js'
- '*.ts'
jobs:
openhands-docs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install OpenHands API helper
run: |
curl -O https://raw.githubusercontent.com/All-Hands-AI/OpenHands/main/scripts/openhands_api.py
python -m pip install --upgrade pip
pip install requests
- name: Generate Documentation with OpenHands
env:
OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -c "
import sys
sys.path.append('.')
from openhands_api import OpenHandsAPI
# Create documentation prompt
prompt = '''Please review the current codebase and update the project documentation:
Repository: ${{ github.repository }}
Tasks:
1. Review the README.md and ensure it accurately reflects the current project state
2. Update API documentation if there are new endpoints or changes
3. Check that code examples in documentation are up-to-date
4. Add documentation for any new features or significant changes
5. Ensure installation and setup instructions are current
6. Update any outdated links or references
7. Add or improve code comments where needed
Please create a pull request with your documentation updates if changes are needed.
Focus on clarity, accuracy, and completeness.
'''
# Start OpenHands conversation
client = OpenHandsAPI()
response = client.create_conversation(
initial_user_msg=prompt,
repository='${{ github.repository }}'
)
print(f'Started OpenHands documentation update: {response.get(\"conversation_id\", \"Unknown\")}')
# Poll for completion (optional - remove if you want fire-and-forget)
try:
final_response = client.poll_until_stopped(response['conversation_id'], timeout=900)
print(f'Documentation update completed with status: {final_response.get(\"status\", \"Unknown\")}')
except Exception as e:
print(f'Documentation update may still be running: {e}')
"

120
scripts/openhands_api.py Normal file
View File

@@ -0,0 +1,120 @@
"""OpenHands API Python helper for automation tasks.
Default base_url is https://app.all-hands.dev.
"""
from __future__ import annotations
import os
import time
from typing import Any
import requests
class OpenHandsAPI:
"""Minimal client for interacting with the OpenHands API."""
def __init__(
self,
api_key: str | None = None,
base_url: str = 'https://app.all-hands.dev',
):
"""Initialize the API client.
Args:
api_key: OpenHands API key. If not provided, will use OPENHANDS_API_KEY env var.
base_url: Base URL for the OpenHands API. Defaults to https://app.all-hands.dev.
"""
self.api_key = api_key or os.getenv('OPENHANDS_API_KEY')
if not self.api_key:
raise ValueError(
'API key is required. Set OPENHANDS_API_KEY environment variable or pass api_key parameter.'
)
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update(
{
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json',
}
)
def create_conversation(
self,
initial_user_msg: str,
repository: str | None = None,
selected_branch: str | None = None,
) -> dict[str, Any]:
"""Create a new conversation.
Args:
initial_user_msg: The initial message to start the conversation
repository: Git repository name in format "owner/repo" (optional)
selected_branch: Git branch to use (optional)
Returns:
Response containing conversation_id and status
"""
conversation_data: dict[str, Any] = {'initial_user_msg': initial_user_msg}
if repository:
conversation_data['repository'] = repository
if selected_branch:
conversation_data['selected_branch'] = selected_branch
response = self.session.post(
f'{self.base_url}/api/conversations', json=conversation_data
)
response.raise_for_status()
return response.json()
def get_conversation(self, conversation_id: str) -> dict[str, Any]:
"""Get conversation status and details."""
response = self.session.get(
f'{self.base_url}/api/conversations/{conversation_id}'
)
response.raise_for_status()
return response.json()
def get_trajectory(self, conversation_id: str) -> dict[str, Any]:
"""Get the trajectory (event history) for a conversation."""
response = self.session.get(
f'{self.base_url}/api/conversations/{conversation_id}/trajectory'
)
response.raise_for_status()
return response.json()
def poll_until_stopped(
self, conversation_id: str, timeout: int = 1200, poll_interval: int = 30
) -> dict[str, Any]:
"""Poll conversation until it stops or times out."""
start = time.time()
while time.time() - start < timeout:
convo = self.get_conversation(conversation_id)
status = str(convo.get('status', '')).upper()
if status == 'STOPPED':
return convo
if status in ['FAILED', 'ERROR', 'CANCELLED']:
return convo
time.sleep(poll_interval)
raise TimeoutError(
f'Conversation {conversation_id} did not stop within {timeout} seconds'
)
def post_github_comment(
self, repo: str, issue_number: int, comment: str, token: str
) -> None:
"""Post a comment to a GitHub issue/PR."""
url = f'https://api.github.com/repos/{repo}/issues/{issue_number}/comments'
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3+json',
}
data = {'body': comment}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
if __name__ == '__main__':
print('OpenHands API helper - import this module to use the OpenHandsAPI class')