Skip to content
Draft
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
97 changes: 97 additions & 0 deletions .github/workflows/update-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Update Type Annotations Example

on:
workflow_dispatch:
inputs:
branch:
description: "Branch to run on (leave empty for current branch)"
required: false
default: ""
llm_model:
description: "LLM model to use"
required: false
default: "anthropic/claude-sonnet-4-6-20250514"
llm_base_url:
description: "LLM base URL (optional)"
required: false
default: ""
push:
paths:
- "plugins/python-type-annotations/**"
- ".github/workflows/update-example.yaml"

jobs:
update-example:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch || github.ref }}

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

- name: Install OpenHands CLI
run: pip install openhands

- name: Copy before to after
run: |
cp -r plugins/python-type-annotations/examples/basic/before/* \
plugins/python-type-annotations/examples/basic/after/

- name: Set up skills directory
run: |
mkdir -p plugins/python-type-annotations/examples/basic/after/.agents/skills
cp -r plugins/python-type-annotations/skills/* \
plugins/python-type-annotations/examples/basic/after/.agents/skills/

- name: Run OpenHands in headless mode
id: openhands
working-directory: plugins/python-type-annotations/examples/basic/after
env:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_MODEL: ${{ github.event.inputs.llm_model || secrets.LLM_MODEL }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
run: |
# Run headless and capture conversation ID from output
output=$(openhands --headless --override-with-envs -f ../prompt.md 2>&1 | tee /dev/stderr)
conversation_id=$(echo "$output" | grep -oP 'Conversation ID: \K[a-f0-9]+' | tail -1)
echo "conversation_id=$conversation_id" >> $GITHUB_OUTPUT

- name: Export trajectory
run: |
conversation_id="${{ steps.openhands.outputs.conversation_id }}"
if [ -n "$conversation_id" ]; then
# Combine all event files into a single trajectory JSON
events_dir="$HOME/.openhands/conversations/$conversation_id/events"
if [ -d "$events_dir" ]; then
echo '{"conversation_id": "'$conversation_id'", "events": [' > plugins/python-type-annotations/examples/basic/trajectory.json
first=true
for f in "$events_dir"/event-*.json; do
if [ "$first" = true ]; then
first=false
else
echo ',' >> plugins/python-type-annotations/examples/basic/trajectory.json
fi
cat "$f" >> plugins/python-type-annotations/examples/basic/trajectory.json
done
echo ']}' >> plugins/python-type-annotations/examples/basic/trajectory.json
fi
fi

- name: Clean up skills directory
run: |
rm -rf plugins/python-type-annotations/examples/basic/after/.agents

- name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add plugins/python-type-annotations/examples/basic/after/
git add plugins/python-type-annotations/examples/basic/trajectory.json
git diff --cached --quiet || git commit -m "Update type annotations example output"
git push
5 changes: 5 additions & 0 deletions plugins/python-type-annotations/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "python-type-annotations",
"version": "1.0.0",
"description": "Add valid Python type annotations to files or directories"
}
72 changes: 72 additions & 0 deletions plugins/python-type-annotations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Python Type Annotations Plugin

A simple plugin that instructs the agent to add valid Python type annotations to files or directories.

## Features

- Adds type annotations to function parameters and return types
- Annotates class attributes and instance variables
- Uses modern Python 3.9+ syntax (`list[T]` instead of `List[T]`)
- Validates syntax after making changes

## Usage

### With OpenHands CLI

```bash
openhands run \
--plugin github:OpenHands/extensions/plugins/python-type-annotations \
--prompt "Add type annotations to all Python files in src/"
```

### With OpenHands SDK

```python
from openhands.sdk import Agent, Conversation, LLM
from openhands.sdk.plugin import PluginSource

conversation = Conversation(
agent=Agent(llm=LLM(model="anthropic/claude-sonnet-4-20250514")),
plugins=[
PluginSource(
source="github:OpenHands/extensions",
ref="main",
repo_path="plugins/python-type-annotations"
)
]
)

conversation.send_message("Add type annotations to hello.py")
conversation.run()
```

## Examples

See the `examples/basic/` directory for a before/after example:

- `before/hello.py` - Python code without type annotations
- `after/hello.py` - Same code with type annotations added

## Testing

The [`update-example.yaml`](../../.github/workflows/update-example.yaml) GitHub Action can be triggered manually to regenerate the `after/` example using the current plugin and OpenHands CLI. You can run it on any branch or on main. It also runs automatically on any push that changes the plugin.

## Plugin Structure

```
python-type-annotations/
├── .claude-plugin/
│ └── plugin.json
├── skills/
│ └── type-annotations/
│ └── SKILL.md
├── examples/
│ └── basic/
│ ├── prompt.md
│ ├── trajectory.json
│ ├── before/
│ │ └── hello.py
│ └── after/
│ └── hello.py
└── README.md
```
31 changes: 31 additions & 0 deletions plugins/python-type-annotations/examples/basic/after/hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
def greet(name):
"""Return a greeting message."""
return f"Hello, {name}!"


def add(a, b):
"""Add two numbers."""
return a + b


def find_longest(words):
"""Find the longest word in a list."""
if not words:
return None
return max(words, key=len)


class Calculator:
def __init__(self, initial_value):
self.value = initial_value

def add(self, n):
self.value += n
return self

def subtract(self, n):
self.value -= n
return self

def get_value(self):
return self.value
31 changes: 31 additions & 0 deletions plugins/python-type-annotations/examples/basic/before/hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
def greet(name):
"""Return a greeting message."""
return f"Hello, {name}!"


def add(a, b):
"""Add two numbers."""
return a + b


def find_longest(words):
"""Find the longest word in a list."""
if not words:
return None
return max(words, key=len)


class Calculator:
def __init__(self, initial_value):
self.value = initial_value

def add(self, n):
self.value += n
return self

def subtract(self, n):
self.value -= n
return self

def get_value(self):
return self.value
1 change: 1 addition & 0 deletions plugins/python-type-annotations/examples/basic/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add type annotations to all Python files in this directory.

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions plugins/python-type-annotations/skills/type-annotations/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
description: Add valid Python type annotations to functions, methods, and variables
triggers:
- type annotations
- add types
- annotate python
- type hints
---

# Python Type Annotations

Add comprehensive, valid Python type annotations to Python files.

## Instructions

When asked to add type annotations to a Python file or directory:

1. **Analyze the code** to understand:
- Function parameters and their expected types
- Return values
- Class attributes and instance variables
- Module-level variables

2. **Add appropriate type annotations**:
- Use built-in types: `str`, `int`, `float`, `bool`, `bytes`, `None`
- Use `list[T]`, `dict[K, V]`, `set[T]`, `tuple[T, ...]` for collections (Python 3.9+)
- Use `Optional[T]` or `T | None` for nullable types
- Use `Union[A, B]` or `A | B` for multiple possible types
- Use `Any` sparingly, only when the type is truly dynamic
- Use `Callable[[Args], Return]` for function types
- Import from `typing` module when needed: `from typing import Optional, Union, Any, Callable`

3. **Follow best practices**:
- Annotate all function parameters and return types
- Annotate class attributes in `__init__` or as class variables
- Use descriptive type aliases for complex types
- Preserve existing annotations if they are correct
- Do not change the logic or behavior of the code

4. **Validate the annotations**:
- Ensure the file still runs without syntax errors
- Run `python -m py_compile <file>` to check syntax
- If mypy is available, run `mypy <file>` to verify types

## Example Transformation

**Before:**
```python
def greet(name):
return f"Hello, {name}!"

def add_numbers(a, b):
return a + b
```

**After:**
```python
def greet(name: str) -> str:
return f"Hello, {name}!"

def add_numbers(a: int, b: int) -> int:
return a + b
```

## Notes

- Prefer `X | None` over `Optional[X]` for Python 3.10+
- Use `list` instead of `List` for Python 3.9+
- Add `from __future__ import annotations` for forward references in older Python versions