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
36 changes: 36 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copilot Instructions for agunblock Repository

## General Guidelines
- Follow the repository's existing coding style and conventions.
- Prefer TypeScript or JavaScript for new code unless otherwise specified.
- Use Node.js 20 features where appropriate.
- Always use `npm ci` for installing dependencies in CI environments.
- Ensure compatibility with GitHub Actions workflows as defined in `.github/workflows/`.

## GitHub Actions
- Use `actions/checkout@v4` for checking out code.
- Use `actions/setup-node@v4` with `node-version: "20"` and `cache: "npm"` for Node.js setup.
- Keep workflow permissions minimal, e.g., `contents: read` unless more is required.
- Name setup jobs as `copilot-setup-steps` for Copilot compatibility.

## Code Quality
- Write modular, reusable functions.
- Add comments for complex logic.
- Prefer async/await for asynchronous code.
- Use environment variables for secrets and configuration.

## Pull Requests & Commits
- Reference related issues in commit messages and PR descriptions.
- Ensure all workflows pass before merging.

## Dependency Management
- Use `npm ci` for clean, reproducible installs.
- Do not commit `node_modules` or other generated files.

## Security
- Do not hardcode secrets or credentials.
- Use GitHub Actions secrets for sensitive data.

## Documentation
- Update relevant documentation for any new features or changes.
- Use Markdown for documentation files.
30 changes: 30 additions & 0 deletions .github/workflows/codex-playground.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Codex Playground Task
on:
workflow_dispatch:
inputs:
prompt:
description: "Natural-language task for Codex"
required: true
task_id:
description: "Unique task identifier"
required: true
azure_openai_endpoint:
description: "Azure OpenAI endpoint"
required: true
azure_openai_key:
description: "Azure OpenAI API key"
required: true
azure_openai_deployment:
description: "Azure OpenAI deployment name"
required: true
default: "o4-mini"

jobs:
codex-task:
runs-on: ubuntu-latest
timeout-minutes: 30
if: false # This ensures the workflow never runs

steps:
- name: Checkout repository
uses: actions/checkout@v4
38 changes: 38 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: "Copilot Setup Steps"

# Automatically run the setup steps when they are changed to allow for easy validation, and
# allow manual testing through the repository's "Actions" tab
on:
workflow_dispatch:
push:
paths:
- .github/workflows/copilot-setup-steps.yml
pull_request:
paths:
- .github/workflows/copilot-setup-steps.yml

jobs:
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
copilot-setup-steps:
runs-on: ubuntu-latest

# Set the permissions to the lowest permissions possible needed for your steps.
# Copilot will be given its own token for its operations.
permissions:
# If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete.
contents: read

# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install JavaScript dependencies
run: npm ci
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,7 @@ src/nohup.out
frontend/.vite/deps/

get-docker.sh

frontend/pnpm-lock.yaml

frontend/tsconfig.tsbuildinfo
95 changes: 94 additions & 1 deletion backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import asyncio
import os
from .models.schemas import RepositoryAnalysisRequest, RepositoryAnalysisResponse, RepositoryInfoResponse, AnalysisProgressUpdate, TaskBreakdownRequest, TaskBreakdownResponse, Task, DevinSessionRequest, DevinSessionResponse
from .models.schemas import RepositoryAnalysisRequest, RepositoryAnalysisResponse, RepositoryInfoResponse, AnalysisProgressUpdate, TaskBreakdownRequest, TaskBreakdownResponse, Task, DevinSessionRequest, DevinSessionResponse, CodexPlaygroundRequest, CodexPlaygroundResponse, PlaygroundTaskStatus, RunnerTokenRequest
from .services.github import GitHubService
from .services.agent import AzureAgentService
from .config import CORS_ORIGINS
Expand Down Expand Up @@ -340,3 +340,96 @@ async def create_devin_session(request: DevinSessionRequest):
except Exception as e:
logger.error(f"Error creating Devin session: {str(e)}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Failed to create Devin session: {str(e)}")

@app.post("/api/playground/start")
async def start_playground_task(
request: CodexPlaygroundRequest,
github_service: GitHubService = Depends(get_github_service)
):
"""Start a new Codex playground task in GitHub Actions."""
try:
import uuid
task_id = str(uuid.uuid4())

workflow_inputs = {
"prompt": request.prompt,
"task_id": task_id,
"azure_openai_endpoint": request.azure_openai_endpoint,
"azure_openai_key": request.azure_openai_key,
"azure_openai_deployment": request.azure_openai_deployment
}

success = await github_service.dispatch_workflow(
owner=request.owner,
repo=request.repo,
workflow_id="codex-playground.yml",
inputs=workflow_inputs
)

if not success:
raise HTTPException(status_code=500, detail="Failed to dispatch workflow")

return CodexPlaygroundResponse(
task_id=task_id,
status="queued"
)
except Exception as e:
logger.error(f"Error starting playground task: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/playground/status/{task_id}")
async def get_playground_status(
task_id: str,
owner: str,
repo: str,
github_service: GitHubService = Depends(get_github_service)
):
"""Get status of a playground task."""
try:
runs = await github_service.get_workflow_runs(owner, repo, "codex-playground.yml")

matching_run = None
for run in runs:
if task_id in str(run.get("html_url", "")):
matching_run = run
break

if not matching_run:
return PlaygroundTaskStatus(task_id=task_id, status="not_found")

return PlaygroundTaskStatus(
task_id=task_id,
status=matching_run["status"],
workflow_run_id=matching_run["id"],
logs_url=matching_run["html_url"]
)
except Exception as e:
logger.error(f"Error getting playground status: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/playground/logs/{task_id}")
async def get_playground_logs(
task_id: str,
owner: str,
repo: str,
github_service: GitHubService = Depends(get_github_service)
):
"""Get logs URL for a playground task."""
try:
runs = await github_service.get_workflow_runs(owner, repo, "codex-playground.yml")

matching_run = None
for run in runs:
if task_id in str(run.get("html_url", "")):
matching_run = run
break

if not matching_run:
raise HTTPException(status_code=404, detail="Task not found")

logs_url = await github_service.get_workflow_logs(owner, repo, matching_run["id"])

return {"logs_url": logs_url, "workflow_url": matching_run["html_url"]}
except Exception as e:
logger.error(f"Error getting playground logs: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
26 changes: 26 additions & 0 deletions backend/app/models/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,29 @@ class DevinSessionRequest(BaseModel):
class DevinSessionResponse(BaseModel):
session_id: str
session_url: str

class CodexPlaygroundRequest(BaseModel):
owner: str
repo: str
prompt: str
azure_openai_endpoint: str
azure_openai_key: str
azure_openai_deployment: str = "gpt-4o"

class CodexPlaygroundResponse(BaseModel):
task_id: str
workflow_run_id: Optional[int] = None
runner_name: Optional[str] = None
status: str

class PlaygroundTaskStatus(BaseModel):
task_id: str
status: str
workflow_run_id: Optional[int] = None
logs_url: Optional[str] = None
artifacts_url: Optional[str] = None
error: Optional[str] = None

class RunnerTokenRequest(BaseModel):
owner: str
repo: str
65 changes: 65 additions & 0 deletions backend/app/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,3 +261,68 @@ async def get_repository_snapshot(self, owner: str, repo: str) -> Optional[Dict[
print(f"Detailed error: {repr(e)}")

raise RuntimeError(f"Failed to fetch repository data: {error_message}")

async def create_runner_token(self, owner: str, repo: str) -> Optional[Dict[str, Any]]:
"""Create a registration token for GitHub Actions runners."""
try:
response = gh().rest.actions.create_registration_token_for_repo(
owner=owner, repo=repo
).parsed_data
return {
"token": response.token,
"expires_at": response.expires_at
}
except Exception as e:
print(f"Error creating runner token: {str(e)}")
return None

async def dispatch_workflow(self, owner: str, repo: str, workflow_id: str, inputs: Dict[str, str], ref: Optional[str] = None) -> Optional[bool]:
"""Dispatch a workflow with given inputs."""
try:
# Fetch the default branch if ref is not provided
if not ref:
repo_data = gh().rest.repos.get(owner=owner, repo=repo).parsed_data
ref = repo_data.default_branch

gh().rest.actions.create_workflow_dispatch(
owner=owner,
repo=repo,
workflow_id=workflow_id,
ref=ref,
inputs=inputs
)
return True
except Exception as e:
print(f"Error dispatching workflow: {str(e)}")
return False

async def get_workflow_runs(self, owner: str, repo: str, workflow_id: str) -> List[Dict[str, Any]]:
"""Get recent workflow runs for a workflow."""
try:
response = gh().rest.actions.list_workflow_runs(
owner=owner, repo=repo, workflow_id=workflow_id
).parsed_data
return [
{
"id": run.id,
"status": run.status,
"conclusion": run.conclusion,
"created_at": run.created_at,
"html_url": run.html_url
}
for run in response.workflow_runs[:5]
]
except Exception as e:
print(f"Error getting workflow runs: {str(e)}")
return []

async def get_workflow_logs(self, owner: str, repo: str, run_id: int) -> Optional[str]:
"""Get logs for a specific workflow run."""
try:
response = gh().rest.actions.download_workflow_run_logs(
owner=owner, repo=repo, run_id=run_id
)
return response.url if hasattr(response, 'url') else None
except Exception as e:
print(f"Error getting workflow logs: {str(e)}")
return None
Loading
Loading