Graham Neubig a5f61caae9
Add actions for github push and send PR (#1415)
* Added a push action

* Tests

* Add tests

* Fix capitalization

* Update

* Fix typo

* Fix integration tests

* Added poetry.lock

* Set lock

* Fix action parsing

* Update integration test output

* Updated prompt

* Update integration test

* Add github token to default config

---------

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
2024-04-29 00:56:23 +00:00

159 lines
4.9 KiB
Python

from dataclasses import dataclass
from opendevin.observation import Observation, AgentErrorObservation
from opendevin.observation.message import AgentMessageObservation
from opendevin.observation.run import CmdOutputObservation
from opendevin.schema import ActionType
from opendevin import config
from typing import TYPE_CHECKING
import requests
import random
import string
from opendevin.schema.config import ConfigType
from .base import ExecutableAction
if TYPE_CHECKING:
from opendevin.controller import AgentController
@dataclass
class GitHubPushAction(ExecutableAction):
"""This pushes the current branch to github.
To use this, you need to set the GITHUB_TOKEN environment variable.
The agent will return a message with a URL that you can click to make a pull
request.
Attributes:
owner: The owner of the source repo
repo: The name of the source repo
branch: The branch to push
action: The action identifier
"""
owner: str
repo: str
branch: str
action: str = ActionType.PUSH
async def run(self, controller: 'AgentController') -> Observation:
github_token = config.get(ConfigType.GITHUB_TOKEN)
if not github_token:
return AgentErrorObservation(
'GITHUB_TOKEN is not set'
)
# Create a random short string to use as a temporary remote
random_remote = ''.join(
['opendevin_temp_'] + random.choices(string.ascii_lowercase, k=5)
)
# Set the temporary remote
new_url = f'https://{github_token}@github.com/{self.owner}/{self.repo}.git'
command = f'git remote add {random_remote} {new_url}'
remote_add_result = controller.action_manager.run_command(
command, background=False
)
if (
not isinstance(remote_add_result, CmdOutputObservation)
or remote_add_result.exit_code != 0
):
return remote_add_result
# Push the branch to the temporary remote
command = f'git push {random_remote} {self.branch}'
push_result = controller.action_manager.run_command(command, background=False)
# Delete the temporary remote
command = f'git remote remove {random_remote}'
remote_remove_result = controller.action_manager.run_command(
command, background=False
)
if (
not isinstance(remote_remove_result, CmdOutputObservation)
or remote_remove_result.exit_code != 0
):
return remote_remove_result
return push_result
@property
def message(self) -> str:
return f'Pushing branch {self.branch} to {self.owner}/{self.repo}'
@dataclass
class GitHubSendPRAction(ExecutableAction):
"""An action to send a github PR.
To use this, you need to set the GITHUB_TOKEN environment variable.
Attributes:
owner: The owner of the source repo
repo: The name of the source repo
title: The title of the PR
head: The branch to send the PR from
head_repo: The repo to send the PR from
base: The branch to send the PR to
body: The body of the PR
"""
owner: str
repo: str
title: str
head: str
head_repo: str | None
base: str
body: str | None
action: str = ActionType.SEND_PR
async def run(self, controller: 'AgentController') -> Observation:
github_token = config.get(ConfigType.GITHUB_TOKEN)
if not github_token:
return AgentErrorObservation(
'GITHUB_TOKEN is not set'
)
# API URL to create the pull request
url = f'https://api.github.com/repos/{self.owner}/{self.repo}/pulls'
# Headers to authenticate and request JSON responses
headers = {
'Authorization': f'token {github_token}',
'Accept': 'application/vnd.github.v3+json',
}
# Data for the pull request
data = {
'title': self.title,
'head': self.head,
'head_repo': self.head_repo,
'base': self.base,
'body': self.body,
}
data = {k: v for k, v in data.items() if v is not None}
# Make the request
response = requests.post(url, headers=headers, json=data)
# Check for errors
if response.status_code == 201:
return AgentMessageObservation(
'Pull request created successfully!\n'
f'Pull request URL:{response.json()["html_url"]}'
)
else:
return AgentErrorObservation(
'Failed to create pull request\n'
f'Status code: {response.status_code}\n'
f'Response: {response.text}'
)
@property
def message(self) -> str:
return (
f'Sending PR from {self.head_repo}:{self.head} to '
f'{self.owner}:{self.base}'
)