File size: 3,398 Bytes
8412386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d07c0a
8412386
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0d07c0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8412386
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
 * Custom Next.js server with Socket.io
 *
 * Wraps the standard Next.js server to attach a Socket.io instance
 * on the same HTTP port. All existing REST API routes continue to
 * work unchanged — Socket.io upgrades only on the /socket.io path.
 */

const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");
const { Server: SocketIO } = require("socket.io");

const dev = process.env.NODE_ENV !== "production";
const hostname = process.env.HOSTNAME || "0.0.0.0";
const port = parseInt(process.env.PORT || "3001", 10);

const app = next({ dev, hostname, port });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const httpServer = createServer((req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  });

  const io = new SocketIO(httpServer, {
    path: "/socket.io",
    cors: {
      origin: [
        "http://localhost:5173",
        "http://localhost:3000",
        "https://open-triage.vercel.app",
        "https://opentriage.onrender.com",
      ],
      methods: ["GET", "POST"],
      credentials: true,
    },
    // Prefer websocket, fall back to polling for restrictive networks
    transports: ["websocket", "polling"],
  });

  // Store globally so API routes can access it
  globalThis.__socketIO = io;

  io.on("connection", (socket) => {
    console.log(`[Socket.io] Client connected: ${socket.id}`);

    // Clients join a room scoped to their RAG session
    socket.on("join_rag_session", (sessionId) => {
      socket.join(`rag:${sessionId}`);
      console.log(`[Socket.io] ${socket.id} joined rag:${sessionId}`);
    });

    socket.on("leave_rag_session", (sessionId) => {
      socket.leave(`rag:${sessionId}`);
    });

    // Agent session — same room scheme so agent thoughts stream to the client
    socket.on("join_agent_session", (sessionId) => {
      socket.join(`rag:${sessionId}`);
      console.log(`[Socket.io] ${socket.id} joined agent session ${sessionId}`);
    });

    socket.on("leave_agent_session", (sessionId) => {
      socket.leave(`rag:${sessionId}`);
    });

    // HITL — the frontend sends the human's reply to a guidance request
    socket.on("human_reply", ({ sessionId, reply }) => {
      console.log(
        `[Socket.io] human_reply for session ${sessionId}: ${(reply || "").slice(0, 80)}`,
      );

      // Resolve the pending guidance Promise in the agent
      const pendingMap = globalThis.__pendingGuidance;
      if (pendingMap && typeof pendingMap.get === "function") {
        const pending = pendingMap.get(sessionId);
        if (pending && typeof pending.resolve === "function") {
          pending.resolve({
            sessionId,
            thoughtId: pending.request?.thoughtId ?? 0,
            reply: reply || "No reply provided.",
            timestamp: new Date().toISOString(),
          });
          pendingMap.delete(sessionId);
        } else {
          console.warn(
            `[Socket.io] No pending guidance for session ${sessionId}`,
          );
        }
      }
    });

    socket.on("disconnect", (reason) => {
      console.log(`[Socket.io] Client disconnected: ${socket.id} (${reason})`);
    });
  });

  httpServer.listen(port, hostname, () => {
    console.log(`> Server ready on http://${hostname}:${port}`);
    console.log(`> Socket.io attached on the same port`);
  });
});