SPB Git
3.8 KB · 32 lines typescript
1import { z } from "zod";2import { handler, parseBody } from "@/lib/api";3import { db, schema } from "@/db";4import { and, desc, eq } from "drizzle-orm";5import { enqueue, QUEUES } from "@/lib/jobs";6import { audit } from "@/lib/audit";7import { AGENTS, AGENT_BY_ID } from "@/lib/agents/definitions";8import { TOOLS } from "@/lib/agents/tools";9import { ALL_SCOPES, SCOPE_LABELS, DEFAULT_LIMITS, type Scope } from "@/lib/agents/types";10import { parseMeta } from "@/lib/agents/runner";1112const summary = { id: schema.aiExecutions.id, agent: schema.aiExecutions.agent, task: schema.aiExecutions.task, model: schema.aiExecutions.model, status: schema.aiExecutions.status, scopes: schema.aiExecutions.scopes, outcome: schema.aiExecutions.outcome, error: schema.aiExecutions.error, costUsd: schema.aiExecutions.costUsd, tokensIn: schema.aiExecutions.tokensIn, tokensOut: schema.aiExecutions.tokensOut, createdAt: schema.aiExecutions.createdAt, startedAt: schema.aiExecutions.startedAt, finishedAt: schema.aiExecutions.finishedAt, proposedChanges: schema.aiExecutions.proposedChanges, toolsInvoked: schema.aiExecutions.toolsInvoked };1314/** GET → agents (definitions) + last executions. */15export const GET = handler(async ({ req, ws }) => {16  const limit = Math.min(200, Number(new URL(req.url).searchParams.get("limit") ?? 60));17  const rows = await db.select(summary).from(schema.aiExecutions).where(eq(schema.aiExecutions.workspaceId, ws.id)).orderBy(desc(schema.aiExecutions.createdAt)).limit(limit);18  const executions = rows.map((r) => ({ ...r, proposals: (r.proposedChanges ?? []).length, pendingProposals: ((r.proposedChanges ?? []) as any[]).filter((p) => p.status === "proposed").length, toolCalls: ((r.toolsInvoked ?? []) as any[]).filter((s) => s.tool !== "model").length, contextPageId: r.status === "pending" ? parseMeta(r.outcome).pageId ?? null : null, proposedChanges: undefined, toolsInvoked: undefined, outcome: r.status === "pending" ? null : r.outcome }));19  return Response.json({ agents: AGENTS.map((a) => ({ id: a.id, name: a.name, icon: a.icon, description: a.description, tools: a.tools.map((t) => ({ name: t, scope: TOOLS[t]?.scope, proposal: TOOLS[t]?.proposal ?? false })), defaultScopes: a.defaultScopes, allowedScopes: a.allowedScopes })), scopes: ALL_SCOPES.map((s) => ({ id: s, label: SCOPE_LABELS[s], write: s.startsWith("write:") })), limits: DEFAULT_LIMITS, executions });20});21/** POST { agent, task, scopes, pageId?, model? } → creates the execution (pending) and enqueues it. */22export const POST = handler(async ({ req, ws, user }) => {23  const b = await parseBody(req, z.object({ agent: z.string(), task: z.string().min(3).max(20_000), scopes: z.array(z.enum(ALL_SCOPES as [Scope, ...Scope[]])).default([]), pageId: z.string().uuid().nullable().optional(), model: z.string().max(100).nullable().optional() }));24  const def = AGENT_BY_ID[b.agent]; if (!def) return Response.json({ error: "Unknown agent" }, { status: 400 });25  const scopes = [...new Set(b.scopes.filter((s) => def.allowedScopes.includes(s)))];26  if (b.pageId) { const [p] = await db.select({ id: schema.pages.id }).from(schema.pages).where(and(eq(schema.pages.id, b.pageId), eq(schema.pages.workspaceId, ws.id))); if (!p) return Response.json({ error: "Context page not found" }, { status: 404 }); }27  const [row] = await db.insert(schema.aiExecutions).values({ workspaceId: ws.id, agent: def.id, task: b.task, scopes, model: b.model ?? null, status: "pending", outcome: b.pageId ? JSON.stringify({ pageId: b.pageId }) : null }).returning();28  const job = await enqueue(QUEUES.agent, { executionId: row.id, workspaceId: ws.id }, { retryLimit: 1, expireInSeconds: 60 * 30 });29  await audit(user.id, "agent.run", { type: "execution", id: row.id }, { agent: def.id, scopes, pageId: b.pageId ?? null, job });30  return Response.json({ ...row, job }, { status: 201 });31});32
Files · main
Menu

Theme