UNPKG

comfyui-mcp

Version:

Local-first, agent-native control plane for ComfyUI — MCP server + autonomous sidebar agent that drives your live graph in natural language on ANY LLM: Claude/ChatGPT/Gemini on your subscription (no API key), free local models via Ollama (fully offline),

73 lines • 3.33 kB
// --------------------------------------------------------------------------- // AI SDK UI Message Stream Protocol — consumer-side parser. // // `POST /api/chat` returns a Server-Sent Events stream whose every `data:` // frame is a JSON-encoded `UIMessageChunk` (the AI SDK v6 UI message stream // protocol — see `node_modules/ai/dist/index.d.ts` for the full union). The // terminator frame is the literal `data: [DONE]`. // // This module provides a single pure function that turns an arbitrary slice of // the raw text body (i.e. however much arrived in the latest `reader.read()`) // into: // - a list of decoded chunk objects, and // - a `remainder` string (an unterminated partial event) the caller must // prepend to the NEXT slice before re-invoking. // // Keeping the parser pure (no state, no DOM, no fetch) makes it cheap to test // against canned byte streams in vitest. The exact same logic is *also* // inlined into the browser-side panel (web/extensions/...) because that file // is dropped directly into ComfyUI's `web/extensions/` directory with no // bundler; the two implementations are kept byte-equivalent. // --------------------------------------------------------------------------- /** * Parse a slice of an AI SDK UI message stream. Stateless — the caller owns * the remainder buffer. * * Frame grammar: events are separated by `\n\n`; each event is one or more * `data: <payload>` lines. The terminator is the literal `[DONE]`. We * tolerate `\r\n\n` and stray blank lines. */ export function parseUiMessageStream(buffer) { const chunks = []; let done = false; // Normalize line endings so the split below works regardless of transport. const normalized = buffer.replace(/\r\n/g, "\n"); // Split on the blank-line frame boundary. The trailing element is whatever // is *after* the last `\n\n` — i.e. an unterminated frame in progress. const parts = normalized.split("\n\n"); const remainder = parts.pop() ?? ""; for (const frame of parts) { // A frame can contain multiple `data:` lines (SSE spec joins them with // `\n`). In practice AI SDK emits a single line per frame, but we honor // the spec to keep the parser interoperable. const dataLines = []; for (const line of frame.split("\n")) { if (!line.startsWith("data:")) continue; // Strip `data:` then a single optional leading space (per SSE spec). let payload = line.slice(5); if (payload.startsWith(" ")) payload = payload.slice(1); dataLines.push(payload); } if (dataLines.length === 0) continue; const payload = dataLines.join("\n"); if (payload === "[DONE]") { done = true; continue; } try { const parsed = JSON.parse(payload); if (parsed && typeof parsed === "object" && typeof parsed.type === "string") { chunks.push(parsed); } } catch { // Malformed frame — skip silently. The AI SDK never emits invalid // JSON; this is just defensive for non-conforming proxies. } } return { chunks, remainder, done }; } //# sourceMappingURL=ui-message-stream-parser.js.map