AkurAI Build
Menu

popagent

public

Latest change 4565eb356975518dfc0202eab29b56dff51e8f71 - add deterministic agent health validation by AkurAI Build

import { isAbsolute, posix } from "node:path";
import { z } from "zod";
import { codeIntelligence, type CodeIntelligenceService } from "./code-intelligence";
import { agentWorkspaces, type AgentWorkspaceStore } from "./agent-workspaces";

const relativePathSchema = z.string().trim().min(1).max(1_024).refine((value) => {
  const normalized = posix.normalize(value.replaceAll("\\", "/"));
  return !isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith("../");
}, "Path must be repository-relative");
const impactInputSchema = z.object({
  paths: z.array(relativePathSchema).max(8).default([]),
}).strict();

type Service = Pick<CodeIntelligenceService, "codeContext" | "symbolContext" | "changeImpact">;
type WorkspaceStore = Pick<AgentWorkspaceStore, "get">;
type RouteRequest = Request & { params: { id: string } };
type RouteHandler = (request: RouteRequest) => Promise<Response>;

export function createCodeIntelligenceRoutes(options: {
  workspaceStore?: WorkspaceStore;
  service?: Service;
} = {}): Record<string, RouteHandler> {
  const workspaceStore = options.workspaceStore ?? agentWorkspaces;
  const service = options.service ?? codeIntelligence;

  async function workspaceExists(id: string): Promise<boolean> {
    return Boolean(await workspaceStore.get(id));
  }

  return {
    "/api/workspaces/:id/code-context": async (request) => {
      if (!(await workspaceExists(request.params.id))) {
        return Response.json({ error: "Workspace not found" }, { status: 404 });
      }
      const url = new URL(request.url);
      const input = z.object({
        query: z.string().trim().min(1).max(2_000),
        limit: z.coerce.number().int().min(1).max(20).default(8),
      }).safeParse({
        query: url.searchParams.get("q") ?? "",
        limit: url.searchParams.get("limit") ?? undefined,
      });
      if (!input.success) return Response.json({ error: "Invalid code context query", issues: input.error.issues }, { status: 400 });
      try {
        return Response.json(await service.codeContext(request.params.id, input.data.query, input.data.limit));
      } catch (error) {
        return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 503 });
      }
    },
    "/api/workspaces/:id/symbol-context": async (request) => {
      if (!(await workspaceExists(request.params.id))) {
        return Response.json({ error: "Workspace not found" }, { status: 404 });
      }
      const url = new URL(request.url);
      const input = z.object({
        symbol: z.string().trim().min(1).max(512),
        filePath: relativePathSchema.optional(),
      }).safeParse({
        symbol: url.searchParams.get("symbol") ?? "",
        filePath: url.searchParams.get("filePath") ?? undefined,
      });
      if (!input.success) return Response.json({ error: "Invalid symbol context query", issues: input.error.issues }, { status: 400 });
      try {
        return Response.json(await service.symbolContext(
          request.params.id,
          input.data.symbol,
          input.data.filePath,
        ));
      } catch (error) {
        return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 503 });
      }
    },
    "/api/workspaces/:id/change-impact": async (request) => {
      if (!(await workspaceExists(request.params.id))) {
        return Response.json({ error: "Workspace not found" }, { status: 404 });
      }
      const input = impactInputSchema.safeParse(await request.json().catch(() => undefined));
      if (!input.success) return Response.json({ error: "Invalid change impact request", issues: input.error.issues }, { status: 400 });
      try {
        return Response.json(await service.changeImpact(request.params.id, input.data.paths));
      } catch (error) {
        return Response.json({ error: error instanceof Error ? error.message : String(error) }, { status: 503 });
      }
    },
  };
}