Merge main: wave-4 backend (activity abort endpoint, lastStep capture)
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* POST /api/activity/abort — the dock's END/ABORT control, uniform across
|
||||
* process kinds (user-decided contract): body {kind: "job"|"extraction", id}
|
||||
* where id is the jobId for sub-agent jobs and the chatId for extraction
|
||||
* runs (the pipeline registry is chat-keyed). Mechanisms live in their
|
||||
* domains — abortJob (server/jobs.ts, agents stream) and abortExtraction
|
||||
* (the pipeline module, graph stream); this route is the thin composition.
|
||||
*
|
||||
* Outcomes: a job's not_found -> 404, not_running -> 409, aborted -> 200.
|
||||
* Extraction abort is idempotent by design (aborting with nothing in
|
||||
* flight is success — the blob is gone either way) -> 200.
|
||||
*/
|
||||
import { abortExtraction, abortJob } from "@/src/server";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
let body: unknown;
|
||||
try {
|
||||
body = await req.json();
|
||||
} catch {
|
||||
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
||||
}
|
||||
const { kind, id } = (body ?? {}) as { kind?: string; id?: string };
|
||||
if ((kind !== "job" && kind !== "extraction") || typeof id !== "string" || id === "") {
|
||||
return Response.json({ error: 'Body must be {kind: "job"|"extraction", id: string}' }, { status: 400 });
|
||||
}
|
||||
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
|
||||
return Response.json({ error: "Invalid id" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (kind === "job") {
|
||||
const outcome = abortJob(id);
|
||||
if (!outcome.ok) {
|
||||
const status = outcome.reason === "not_found" ? 404 : 409;
|
||||
return Response.json({ ok: false, reason: outcome.reason }, { status });
|
||||
}
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
// kind === "extraction": true cancellation; idempotent (nothing in
|
||||
// flight is success — same dock outcome)
|
||||
abortExtraction(id);
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
@@ -15,6 +15,10 @@ export async function GET() {
|
||||
title: chatStore.getChat(run.chatId)?.title ?? run.chatId,
|
||||
phase: run.phase,
|
||||
startedAt: run.startedAt,
|
||||
// Live per-stage usage (the money channel): model + per-step CallUsage,
|
||||
// accumulated as steps bill. Cost and context-window folds are the
|
||||
// consumer's (core/prices.ts helpers — nothing stores dollars).
|
||||
usage: run.usage,
|
||||
}));
|
||||
return Response.json({ runs });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user