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
2 changes: 2 additions & 0 deletions backend/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from services.dependency_analyzer import DependencyAnalyzer
from services.style_analyzer import StyleAnalyzer
from services.dna_extractor import DNAExtractor
from services.context_assembler import ContextAssembler
from services.rate_limiter import RateLimiter, APIKeyManager
from services.supabase_service import get_supabase_service
from services.input_validator import CostController
Expand All @@ -25,6 +26,7 @@
dependency_analyzer = DependencyAnalyzer()
style_analyzer = StyleAnalyzer()
dna_extractor = DNAExtractor()
context_assembler = ContextAssembler()

# Rate limiting and API key management
rate_limiter = RateLimiter(redis_client=cache.redis if cache.redis else None)
Expand Down
2 changes: 2 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from routes.github import router as github_router
from routes.feedback import router as feedback_router
from routes.admin import router as admin_router
from routes.context import router as context_router
from routes.ws_playground import websocket_playground_index
from routes.ws_repos import websocket_repo_indexing

Expand Down Expand Up @@ -106,6 +107,7 @@ async def dispatch(self, request: Request, call_next):
app.include_router(github_router, prefix=API_PREFIX)
app.include_router(feedback_router, prefix=API_PREFIX)
app.include_router(admin_router, prefix=API_PREFIX)
app.include_router(context_router, prefix=API_PREFIX)

# WebSocket endpoints (versioned)
app.add_api_websocket_route(f"{API_PREFIX}/ws/index/{{repo_id}}", websocket_index)
Expand Down
81 changes: 81 additions & 0 deletions backend/routes/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Context assembly endpoint.

Provides per-task context packaging via POST /api/v1/context/assemble.
Uses semantic search + dependency graph + project rules to build a
minimal, precise context package for AI coding assistants.
"""
import time
from typing import Any

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field

from dependencies import verify_repo_access
from middleware.auth import AuthContext, require_auth
from services.observability import (
add_breadcrumb,
capture_exception,
logger,
metrics,
set_operation_context,
)

router = APIRouter(tags=["context"])


class AssembleRequest(BaseModel):
task: str = Field(..., min_length=3, max_length=1000)
repo_id: str
token_budget: int = Field(default=1500, ge=100, le=10000)


@router.post("/context/assemble")
async def assemble_context(
request: AssembleRequest,
auth: AuthContext = Depends(require_auth),
) -> dict[str, Any]:
"""Assemble task-specific context from semantic search + deps + rules.

Returns a markdown context package sized to fit within token_budget,
containing only the files, dependencies, and project rules relevant
to the given task description.
"""
set_operation_context(
"context_assemble",
user_id=auth.user_id,
repo_id=request.repo_id,
)
add_breadcrumb("Context assembly requested", category="context", repo_id=request.repo_id)

verify_repo_access(request.repo_id, auth.user_id)

from dependencies import context_assembler

start = time.time()
try:
result = await context_assembler.assemble(
task=request.task,
repo_id=request.repo_id,
user_id=auth.user_id,
token_budget=request.token_budget,
)

elapsed = time.time() - start
logger.info(
"Context assembled",
repo_id=request.repo_id,
files=result["files_found"],
tokens=result["tokens_used"],
budget=request.token_budget,
duration_ms=round(elapsed * 1000),
)
Comment thread
DevanshuNEU marked this conversation as resolved.
metrics.timing("context_assemble_ms", elapsed * 1000)

return result

except HTTPException:
raise
except Exception as exc:
capture_exception(exc, operation="context_assemble", repo_id=request.repo_id)
logger.error("Context assembly failed: %s", exc)
raise HTTPException(status_code=500, detail="Context assembly failed")
Loading