route.ts 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. import {
  2. createUIMessageStream,
  3. createUIMessageStreamResponse,
  4. type UIMessage,
  5. } from "ai";
  6. import { pipeJsonRender } from "@json-render/core";
  7. import { getSession, dropSession } from "@/lib/agent";
  8. import { DEFAULT_AGENT_ID, isAgentId } from "@/lib/agents";
  9. // Harness turns are long: the agent boots a sandbox, edits files, and runs
  10. // commands before answering.
  11. export const maxDuration = 600;
  12. function lastUserText(messages: UIMessage[]): string | null {
  13. for (let i = messages.length - 1; i >= 0; i--) {
  14. const message = messages[i];
  15. if (message?.role !== "user") continue;
  16. const text = message.parts
  17. .filter((part) => part.type === "text")
  18. .map((part) => part.text)
  19. .join("\n")
  20. .trim();
  21. return text.length > 0 ? text : null;
  22. }
  23. return null;
  24. }
  25. export async function POST(req: Request) {
  26. const body = await req.json();
  27. const chatId: string = body.id ?? "default";
  28. const messages: UIMessage[] = body.messages ?? [];
  29. const prompt = lastUserText(messages);
  30. if (!prompt) {
  31. return new Response(
  32. JSON.stringify({ error: "a user message with text is required" }),
  33. { status: 400, headers: { "Content-Type": "application/json" } },
  34. );
  35. }
  36. // The agent is chosen on the first message and locked for the chat: an
  37. // existing session ignores `body.agent` and keeps its original agent.
  38. const requestedAgent = isAgentId(body.agent) ? body.agent : DEFAULT_AGENT_ID;
  39. // The harness session owns its own conversation history, so each turn
  40. // sends only the fresh user input -- not the whole transcript.
  41. const { session, agent } = await getSession(chatId, requestedAgent);
  42. const result = await agent.stream({ prompt, session });
  43. const stream = createUIMessageStream({
  44. execute: async ({ writer }) => {
  45. writer.merge(pipeJsonRender(result.toUIMessageStream()));
  46. },
  47. });
  48. return createUIMessageStreamResponse({ stream });
  49. }
  50. export async function DELETE(req: Request) {
  51. const { searchParams } = new URL(req.url);
  52. const chatId = searchParams.get("id") ?? "default";
  53. dropSession(chatId);
  54. return new Response(null, { status: 204 });
  55. }