AkurAI Build
Menu

popagent

public

Latest change 7f0ff66d6d9fb6468416c58bee46bd3d08169501 - Checkpoint browser channels and memory work by AkurAI Build

import type { ChannelEvent } from "../api-types";
import { apiFetch } from "./api";

export function parseChannelEventBlock(block: string): ChannelEvent | undefined {
  const data = block.split("\n")
    .filter((line) => line.startsWith("data:"))
    .map((line) => line.slice(5).trimStart())
    .join("\n");
  if (!data) return undefined;
  try {
    const event = JSON.parse(data) as ChannelEvent;
    return event?.type && event.channelId ? event : undefined;
  } catch {
    return undefined;
  }
}

export async function consumeChannelEvents(
  channelId: string,
  signal: AbortSignal,
  onEvent: (event: ChannelEvent) => void,
  onOpen?: () => void,
): Promise<void> {
  const response = await apiFetch(`/api/channels/${encodeURIComponent(channelId)}/events`, { signal });
  if (!response.ok || !response.body) throw new Error(`Channel stream failed: HTTP ${response.status}`);
  onOpen?.();
  const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
  let buffer = "";
  while (!signal.aborted) {
    const { done, value } = await reader.read();
    if (done) return;
    buffer += value.replaceAll("\r\n", "\n");
    let boundary = buffer.indexOf("\n\n");
    while (boundary >= 0) {
      const event = parseChannelEventBlock(buffer.slice(0, boundary));
      if (event) onEvent(event);
      buffer = buffer.slice(boundary + 2);
      boundary = buffer.indexOf("\n\n");
    }
  }
}