refactoring the api endpoints to be separate files to reduce context usage

This commit is contained in:
Cody Seibert
2025-12-14 17:53:21 -05:00
parent cdc8334d82
commit 6b30271441
121 changed files with 4281 additions and 2927 deletions

View File

@@ -0,0 +1,36 @@
/**
* Common utilities and state for suggestions routes
*/
import { createLogger } from "../../lib/logger.js";
const logger = createLogger("Suggestions");
// Shared state for tracking generation status
export let isRunning = false;
export let currentAbortController: AbortController | null = null;
/**
* Set the running state and abort controller
*/
export function setRunningState(
running: boolean,
controller: AbortController | null = null
): void {
isRunning = running;
currentAbortController = controller;
}
/**
* Get error message from error object
*/
export function getErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : "Unknown error";
}
/**
* Log error details consistently
*/
export function logError(error: unknown, context: string): void {
logger.error(`${context}:`, error);
}

View File

@@ -0,0 +1,125 @@
/**
* Business logic for generating suggestions
*/
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import type { EventEmitter } from "../../lib/events.js";
import { createLogger } from "../../lib/logger.js";
const logger = createLogger("Suggestions");
export async function generateSuggestions(
projectPath: string,
suggestionType: string,
events: EventEmitter,
abortController: AbortController
): Promise<void> {
const typePrompts: Record<string, string> = {
features:
"Analyze this project and suggest new features that would add value.",
refactoring: "Analyze this project and identify refactoring opportunities.",
security:
"Analyze this project for security vulnerabilities and suggest fixes.",
performance:
"Analyze this project for performance issues and suggest optimizations.",
};
const prompt = `${typePrompts[suggestionType] || typePrompts.features}
Look at the codebase and provide 3-5 concrete suggestions.
For each suggestion, provide:
1. A category (e.g., "User Experience", "Security", "Performance")
2. A clear description of what to implement
3. Concrete steps to implement it
4. Priority (1=high, 2=medium, 3=low)
5. Brief reasoning for why this would help
Format your response as JSON:
{
"suggestions": [
{
"id": "suggestion-123",
"category": "Category",
"description": "What to implement",
"steps": ["Step 1", "Step 2"],
"priority": 1,
"reasoning": "Why this helps"
}
]
}`;
events.emit("suggestions:event", {
type: "suggestions_progress",
content: `Starting ${suggestionType} analysis...\n`,
});
const options: Options = {
model: "claude-opus-4-5-20251101",
maxTurns: 5,
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "acceptEdits",
abortController,
};
const stream = query({ prompt, options });
let responseText = "";
for await (const msg of stream) {
if (msg.type === "assistant" && msg.message.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
responseText = block.text;
events.emit("suggestions:event", {
type: "suggestions_progress",
content: block.text,
});
} else if (block.type === "tool_use") {
events.emit("suggestions:event", {
type: "suggestions_tool",
tool: block.name,
input: block.input,
});
}
}
} else if (msg.type === "result" && msg.subtype === "success") {
responseText = msg.result || responseText;
}
}
// Parse suggestions from response
try {
const jsonMatch = responseText.match(/\{[\s\S]*"suggestions"[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
events.emit("suggestions:event", {
type: "suggestions_complete",
suggestions: parsed.suggestions.map(
(s: Record<string, unknown>, i: number) => ({
...s,
id: s.id || `suggestion-${Date.now()}-${i}`,
})
),
});
} else {
throw new Error("No valid JSON found in response");
}
} catch (error) {
// Return generic suggestions if parsing fails
events.emit("suggestions:event", {
type: "suggestions_complete",
suggestions: [
{
id: `suggestion-${Date.now()}-0`,
category: "Analysis",
description: "Review the AI analysis output for insights",
steps: ["Review the generated analysis"],
priority: 1,
reasoning:
"The AI provided analysis but suggestions need manual review",
},
],
});
}
}

View File

@@ -0,0 +1,19 @@
/**
* Suggestions routes - HTTP API for AI-powered feature suggestions
*/
import { Router } from "express";
import type { EventEmitter } from "../../lib/events.js";
import { createGenerateHandler } from "./routes/generate.js";
import { createStopHandler } from "./routes/stop.js";
import { createStatusHandler } from "./routes/status.js";
export function createSuggestionsRoutes(events: EventEmitter): Router {
const router = Router();
router.post("/generate", createGenerateHandler(events));
router.post("/stop", createStopHandler());
router.get("/status", createStatusHandler());
return router;
}

View File

@@ -0,0 +1,62 @@
/**
* POST /generate endpoint - Generate suggestions
*/
import type { Request, Response } from "express";
import type { EventEmitter } from "../../../lib/events.js";
import { createLogger } from "../../../lib/logger.js";
import {
isRunning,
setRunningState,
getErrorMessage,
logError,
} from "../common.js";
import { generateSuggestions } from "../generate-suggestions.js";
const logger = createLogger("Suggestions");
export function createGenerateHandler(events: EventEmitter) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, suggestionType = "features" } = req.body as {
projectPath: string;
suggestionType?: string;
};
if (!projectPath) {
res.status(400).json({ success: false, error: "projectPath required" });
return;
}
if (isRunning) {
res.json({
success: false,
error: "Suggestions generation is already running",
});
return;
}
setRunningState(true);
const abortController = new AbortController();
setRunningState(true, abortController);
// Start generation in background
generateSuggestions(projectPath, suggestionType, events, abortController)
.catch((error) => {
logger.error("[Suggestions] Error:", error);
events.emit("suggestions:event", {
type: "suggestions_error",
error: getErrorMessage(error),
});
})
.finally(() => {
setRunningState(false, null);
});
res.json({ success: true });
} catch (error) {
logError(error, "Generate suggestions failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}

View File

@@ -0,0 +1,17 @@
/**
* GET /status endpoint - Get status
*/
import type { Request, Response } from "express";
import { isRunning, getErrorMessage, logError } from "../common.js";
export function createStatusHandler() {
return async (_req: Request, res: Response): Promise<void> => {
try {
res.json({ success: true, isRunning });
} catch (error) {
logError(error, "Get status failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}

View File

@@ -0,0 +1,26 @@
/**
* POST /stop endpoint - Stop suggestions generation
*/
import type { Request, Response } from "express";
import {
currentAbortController,
setRunningState,
getErrorMessage,
logError,
} from "../common.js";
export function createStopHandler() {
return async (_req: Request, res: Response): Promise<void> => {
try {
if (currentAbortController) {
currentAbortController.abort();
}
setRunningState(false, null);
res.json({ success: true });
} catch (error) {
logError(error, "Stop suggestions failed");
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}