mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { resolveSessionPath } from "@/lib/session-reader";
|
|
import { getRpcSession, startRpcSession } from "@/lib/rpc-manager";
|
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// GET /api/agent/[id]/events - SSE stream of agent events
|
|
export async function GET(
|
|
req: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
const { id } = await params;
|
|
|
|
// Fast path: already-running session
|
|
let session = getRpcSession(id);
|
|
if (!session || !session.isAlive()) {
|
|
const filePath = await resolveSessionPath(id);
|
|
if (!filePath) {
|
|
return new Response("Session not found", { status: 404 });
|
|
}
|
|
const cwd = SessionManager.open(filePath).getHeader()?.cwd ?? process.cwd();
|
|
try {
|
|
({ session } = await startRpcSession(id, filePath, cwd));
|
|
} catch (error) {
|
|
return new Response(`Failed to start agent: ${error}`, { status: 500 });
|
|
}
|
|
}
|
|
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
const encode = (data: unknown) => {
|
|
const text = `data: ${JSON.stringify(data)}\n\n`;
|
|
controller.enqueue(new TextEncoder().encode(text));
|
|
};
|
|
|
|
// Send initial connected event
|
|
encode({ type: "connected", sessionId: id });
|
|
|
|
const unsubscribe = session.onEvent((event) => {
|
|
encode(event);
|
|
});
|
|
|
|
// Heartbeat every 30s to prevent server/proxy timeout (Next.js default ~120-150s)
|
|
const heartbeat = setInterval(() => {
|
|
try {
|
|
controller.enqueue(new TextEncoder().encode(":\n\n"));
|
|
} catch {
|
|
// controller already closed
|
|
}
|
|
}, 30_000);
|
|
|
|
// Cleanup when client disconnects
|
|
const cleanup = () => {
|
|
clearInterval(heartbeat);
|
|
unsubscribe();
|
|
controller.close();
|
|
};
|
|
|
|
// Detect client disconnect via abort signal
|
|
req.signal?.addEventListener("abort", cleanup);
|
|
},
|
|
});
|
|
|
|
return new Response(stream, {
|
|
headers: {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache",
|
|
Connection: "keep-alive",
|
|
},
|
|
});
|
|
}
|