import { authHeaders } from "./api.js";
export function streamLogs(runId, handlers) {
const ctrl = new AbortController();
(async () => {
let resp;
try {
resp = await fetch(`/v1/runs/${encodeURIComponent(runId)}/logs`, {
headers: authHeaders({ Accept: "text/event-stream" }),
signal: ctrl.signal,
});
} catch (err) {
if (!ctrl.signal.aborted) handlers.onError?.(err);
return;
}
if (resp.status === 404) {
handlers.onExpired?.();
return;
}
if (!resp.ok || !resp.body) {
handlers.onError?.(new Error(`logs HTTP ${resp.status}`));
return;
}
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = "";
try {
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
let idx;
while ((idx = buf.indexOf("\n\n")) !== -1) {
dispatch(buf.slice(0, idx), handlers);
buf = buf.slice(idx + 2);
}
}
} catch (err) {
if (!ctrl.signal.aborted) handlers.onError?.(err);
}
})();
return ctrl;
}
function dispatch(frame, handlers) {
let event = "message";
const data = [];
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
}
const payload = data.join("\n");
if (event === "log") handlers.onLog?.(payload);
else if (event === "truncated") handlers.onTruncated?.(payload);
else if (event === "end") handlers.onEnd?.();
}