myrmidon / python /src /server /api_routes /bug_report_api.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
9.3 kB
"""
Bug Report API for Archon Beta
Handles bug report submission to GitHub Issues with automatic context formatting.
"""
import os
from typing import Any
import httpx
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from ..config.logfire_config import get_logger
logger = get_logger(__name__)
router = APIRouter(prefix="/api/bug-report", tags=["bug-report"])
class BugContext(BaseModel):
error: dict[str, Any]
app: dict[str, Any]
system: dict[str, Any]
services: dict[str, bool]
logs: list[str]
class BugReportRequest(BaseModel):
title: str
description: str
stepsToReproduce: str
expectedBehavior: str
actualBehavior: str
severity: str
component: str
context: BugContext
class BugReportResponse(BaseModel):
success: bool
issue_number: int | None = None
issue_url: str | None = None
message: str
class GitHubService:
def __init__(self):
self.token = os.getenv("GITHUB_TOKEN")
self.repo = os.getenv("GITHUB_REPO", "dynamous-community/Archon-V2-Alpha")
async def create_issue(self, bug_report: BugReportRequest) -> dict[str, Any]:
"""Create a GitHub issue from a bug report."""
if not self.token:
raise HTTPException(status_code=500, detail="GitHub integration not configured - GITHUB_TOKEN not found")
# Format the issue body
issue_body = self._format_issue_body(bug_report)
issue_data = {
"title": bug_report.title,
"body": issue_body,
"labels": [
"bug",
"auto-report",
f"severity:{bug_report.severity}",
f"component:{bug_report.component}",
],
}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"https://api.github.com/repos/{self.repo}/issues",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/vnd.github.v3+json",
"User-Agent": "Archon-Bug-Reporter/1.0",
},
json=issue_data,
)
if response.status_code == 201:
issue_data = response.json()
return {
"success": True,
"issue_number": issue_data["number"],
"issue_url": issue_data["html_url"],
}
elif response.status_code == 401:
logger.error("GitHub API authentication failed")
raise HTTPException(
status_code=500,
detail="GitHub authentication failed - check GITHUB_TOKEN permissions",
)
else:
logger.error(f"GitHub API error: {response.status_code} - {response.text}")
raise HTTPException(status_code=500, detail=f"GitHub API error: {response.status_code}")
except httpx.TimeoutException as e:
logger.error("GitHub API request timed out")
raise HTTPException(status_code=500, detail="GitHub API request timed out") from e
except Exception as e:
logger.error(f"Unexpected error creating GitHub issue: {e}")
raise HTTPException(status_code=500, detail=f"Failed to create GitHub issue: {str(e)}") from e
def _format_issue_body(self, bug_report: BugReportRequest) -> str:
"""Format the bug report as a GitHub issue body."""
# Map severity to emoji
severity_map = {"low": "🟒", "medium": "🟑", "high": "🟠", "critical": "πŸ”΄"}
# Map component to emoji
component_map = {
"knowledge-base": "πŸ”",
"mcp-integration": "πŸ”—",
"projects-tasks": "πŸ“‹",
"settings": "βš™οΈ",
"ui": "πŸ–₯️",
"infrastructure": "🐳",
"not-sure": "❓",
}
severity_emoji = severity_map.get(bug_report.severity, "❓")
component_emoji = component_map.get(bug_report.component, "❓")
return f"""## {severity_emoji} Bug Report
**Reported by:** User (Archon Beta)
**Severity:** {severity_emoji} {bug_report.severity.title()}
**Component:** {component_emoji} {bug_report.component.replace("-", " ").title()}
**Version:** {bug_report.context.app.get("version", "unknown")}
**Platform:** {bug_report.context.system.get("platform", "unknown")}
### Description
{bug_report.description}
### Steps to Reproduce
{bug_report.stepsToReproduce or "Not specified"}
### Expected Behavior
{bug_report.expectedBehavior or "Not specified"}
### Actual Behavior
{bug_report.actualBehavior or "Not specified"}
---
## πŸ”§ Technical Context
### Error Details
```
Error: {bug_report.context.error.get("name", "Unknown")}
Message: {bug_report.context.error.get("message", "No message")}
Stack Trace:
{bug_report.context.error.get("stack", "No stack trace available")}
```
### System Information
- **Platform:** {bug_report.context.system.get("platform", "unknown")}
- **Version:** {bug_report.context.app.get("version", "unknown")}
- **URL:** {bug_report.context.app.get("url", "unknown")}
- **Timestamp:** {bug_report.context.app.get("timestamp", "unknown")}
- **Memory:** {bug_report.context.system.get("memory", "unknown")}
### Service Status
- **Server:** {"βœ…" if bug_report.context.services.get("server") else "❌"}
- **MCP:** {"βœ…" if bug_report.context.services.get("mcp") else "❌"}
- **Agents:** {"βœ…" if bug_report.context.services.get("agents") else "❌"}
### Recent Logs
```
{chr(10).join(bug_report.context.logs[-10:]) if bug_report.context.logs else "No logs available"}
```
---
*Auto-generated by Archon Bug Reporter*
"""
# Global GitHub service instance
github_service = GitHubService()
@router.post("/github", response_model=BugReportResponse)
async def create_github_issue(bug_report: BugReportRequest):
"""
Create a GitHub issue from a bug report.
For open source: If no GitHub token is configured, returns a pre-filled
GitHub issue creation URL for the user to submit manually.
For maintainers: If GitHub token exists, creates the issue directly via API.
"""
logger.info(
f"Processing bug report: {bug_report.title} (severity: {bug_report.severity}, component: {bug_report.component})"
)
# Check if we have GitHub token (maintainer mode)
if github_service.token:
try:
result = await github_service.create_issue(bug_report)
logger.info(f"Successfully created GitHub issue #{result['issue_number']}: {result['issue_url']}")
return BugReportResponse(
success=True,
issue_number=result["issue_number"],
issue_url=result["issue_url"],
message=f"Bug report created as issue #{result['issue_number']}",
)
except HTTPException:
# If API fails, fall back to manual submission
logger.warning("GitHub API failed, falling back to manual submission")
return _create_manual_submission_response(bug_report)
except Exception as e:
logger.error(f"GitHub API error: {e}, falling back to manual submission")
return _create_manual_submission_response(bug_report)
# No token (open source user mode) - create manual submission URL
else:
logger.info("No GitHub token configured, creating manual submission URL")
return _create_manual_submission_response(bug_report)
def _create_manual_submission_response(bug_report: BugReportRequest) -> BugReportResponse:
"""Create a response with pre-filled GitHub issue URL for manual submission."""
# Format the issue body for URL encoding
issue_body = github_service._format_issue_body(bug_report)
# Create pre-filled GitHub issue URL
import urllib.parse
base_url = f"https://github.com/{github_service.repo}/issues/new"
params = {
"template": "bug_report.yml",
"title": bug_report.title,
"labels": f"bug,auto-report,severity:{bug_report.severity},component:{bug_report.component}",
}
# Add the formatted body as a parameter
params["body"] = issue_body
# Build the URL
query_string = urllib.parse.urlencode(params)
github_url = f"{base_url}?{query_string}"
return BugReportResponse(
success=True,
issue_number=None,
issue_url=github_url,
message="Click the provided URL to submit your bug report to GitHub",
)
@router.get("/health")
async def bug_report_health():
"""Health check for bug reporting service."""
github_configured = bool(os.getenv("GITHUB_TOKEN"))
repo_configured = bool(os.getenv("GITHUB_REPO"))
return {
"status": "healthy" if github_configured else "degraded",
"github_token_configured": github_configured,
"github_repo_configured": repo_configured,
"repo": os.getenv("GITHUB_REPO", "dynamous-community/Archon-V2-Alpha"),
"message": "Bug reporting is ready" if github_configured else "GitHub token not configured",
}