UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

502 lines 14.3 kB
/** * Data Stream Protocol Implementation * Implements a protocol for streaming structured data between server and client * Compatible with AI SDK's data stream format */ // ============================================ // Data Stream Writer Implementation // ============================================ /** * Creates a data stream writer */ export function createDataStreamWriter(config) { const { write, close, format = "sse", includeTimestamps = true } = config; const formatEvent = (event) => { if (format === "sse") { // Server-Sent Events format const eventLine = `event: ${event.type}`; const dataLine = `data: ${JSON.stringify(event.data)}`; const idLine = event.id ? `id: ${event.id}` : ""; const lines = [eventLine, dataLine]; if (idLine) { lines.push(idLine); } return lines.join("\n") + "\n\n"; } else { // NDJSON format return JSON.stringify(event) + "\n"; } }; const createEvent = (type, data, id) => ({ type, id, timestamp: includeTimestamps ? Date.now() : 0, data, }); const writeEvent = async (event) => { const formatted = formatEvent(event); await write(formatted); }; return { async writeTextStart(id) { await writeEvent(createEvent("text-start", { id }, id)); }, async writeTextDelta(id, delta) { await writeEvent(createEvent("text-delta", { id, delta }, id)); }, async writeTextEnd(id) { await writeEvent(createEvent("text-end", { id }, id)); }, async writeToolCall(toolCall) { await writeEvent(createEvent("tool-call", toolCall, toolCall.id)); }, async writeToolResult(toolResult) { await writeEvent(createEvent("tool-result", toolResult, toolResult.id)); }, async writeData(data) { await writeEvent(createEvent("data", data)); }, async writeError(error) { await writeEvent(createEvent("error", error)); }, async close() { if (close) { await close(); } }, }; } // ============================================ // Data Stream Response // ============================================ /** * Configuration for DataStreamResponse */ /** * Data stream response class * Creates a streaming response with writer interface */ export class DataStreamResponse { writer = null; controller = null; keepAliveTimer = null; closed = false; encoder = new TextEncoder(); /** The readable stream */ stream; /** Response headers */ headers; constructor(config = {}) { const { contentType = "text/event-stream", headers = {}, keepAliveInterval, includeTimestamps = true, } = config; this.headers = { "Content-Type": contentType, "Cache-Control": "no-cache", Connection: "keep-alive", ...headers, }; const format = contentType === "text/event-stream" ? "sse" : "ndjson"; // Create the readable stream this.stream = new ReadableStream({ start: (controller) => { this.controller = controller; // Create the writer this.writer = createDataStreamWriter({ write: (chunk) => { if (!this.closed && this.controller) { this.controller.enqueue(this.encoder.encode(chunk)); } }, close: () => { this.closeStream(); }, format, includeTimestamps, }); // Set up keep-alive if configured if (keepAliveInterval && keepAliveInterval > 0) { this.keepAliveTimer = setInterval(() => { if (!this.closed && this.controller) { // Send SSE comment as keep-alive const keepAlive = format === "sse" ? ": keep-alive\n\n" : '{"type":"keep-alive"}\n'; this.controller.enqueue(this.encoder.encode(keepAlive)); } }, keepAliveInterval); } }, cancel: () => { this.closeStream(); }, }); } /** * Get the data stream writer */ getWriter() { if (!this.writer) { throw new Error("Stream not initialized"); } return this.writer; } /** * Write text start event */ async writeTextStart(id) { this.getWriter().writeTextStart(id); } /** * Write text delta event */ async writeTextDelta(id, delta) { this.getWriter().writeTextDelta(id, delta); } /** * Write text end event */ async writeTextEnd(id) { this.getWriter().writeTextEnd(id); } /** * Write tool call event */ async writeToolCall(toolCall) { this.getWriter().writeToolCall(toolCall); } /** * Write tool result event */ async writeToolResult(toolResult) { this.getWriter().writeToolResult(toolResult); } /** * Write arbitrary data event */ async writeData(data) { this.getWriter().writeData(data); } /** * Write error event */ async writeError(error) { this.getWriter().writeError(error); } /** * Write finish event and close the stream */ async finish(options) { if (!this.closed && this.controller) { const event = { type: "finish", timestamp: Date.now(), data: { reason: options?.reason, usage: options?.usage, }, }; const formatted = this.headers["Content-Type"] === "text/event-stream" ? `event: finish\ndata: ${JSON.stringify(event.data)}\n\n` : JSON.stringify(event) + "\n"; this.controller.enqueue(this.encoder.encode(formatted)); } this.closeStream(); } /** * Close the stream */ close() { this.closeStream(); } /** * Check if stream is closed */ isClosed() { return this.closed; } closeStream() { if (this.closed) { return; } this.closed = true; if (this.keepAliveTimer) { clearInterval(this.keepAliveTimer); this.keepAliveTimer = null; } if (this.controller) { try { this.controller.close(); } catch { // Ignore errors from already closed controller } this.controller = null; } } } // ============================================ // Helper Functions // ============================================ /** * Create a data stream response */ export function createDataStreamResponse(config) { return new DataStreamResponse(config); } /** * Pipe an async iterable to a data stream response */ export async function pipeAsyncIterableToDataStream(iterable, response, options) { const textId = options?.textId ?? `text-${Date.now()}`; try { await response.writeTextStart(textId); for await (const chunk of iterable) { if (options?.onChunk) { options.onChunk(chunk); } if (typeof chunk === "string") { await response.writeTextDelta(textId, chunk); } else if (typeof chunk === "object" && chunk !== null) { const chunkObj = chunk; // Handle different chunk types if ("textDelta" in chunkObj) { await response.writeTextDelta(textId, String(chunkObj.textDelta)); } else if ("toolCall" in chunkObj) { const toolCall = chunkObj.toolCall; await response.writeToolCall(toolCall); } else if ("toolResult" in chunkObj) { const toolResult = chunkObj.toolResult; await response.writeToolResult(toolResult); } else { await response.writeData(chunk); } } } await response.writeTextEnd(textId); await response.finish({ reason: "stop" }); } catch (error) { if (options?.onError) { options.onError(error); } await response.writeError({ message: error instanceof Error ? error.message : String(error), code: "STREAM_ERROR", }); response.close(); throw error; } } /** * Create SSE headers for streaming responses */ export function createSSEHeaders(additionalHeaders) { return { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "X-Accel-Buffering": "no", ...additionalHeaders, }; } /** * Create NDJSON headers for streaming responses */ export function createNDJSONHeaders(additionalHeaders) { return { "Content-Type": "application/x-ndjson", "Cache-Control": "no-cache", Connection: "keep-alive", ...additionalHeaders, }; } // ============================================ // SSE Event Formatting (Legacy Compatibility) // ============================================ /** * SSE Event options for formatSSEEvent */ /** * Format a Server-Sent Events (SSE) message * * @param options SSE event options * @returns Formatted SSE string * * @example * ```typescript * formatSSEEvent({ data: "Hello world" }); * // => "data: Hello world\n\n" * * formatSSEEvent({ event: "message", data: "Test" }); * // => "event: message\ndata: Test\n\n" * * formatSSEEvent({ data: "Line 1\nLine 2" }); * // => "data: Line 1\ndata: Line 2\n\n" * ``` */ export function formatSSEEvent(options) { const lines = []; if (options.id) { lines.push(`id: ${options.id}`); } if (options.event) { lines.push(`event: ${options.event}`); } if (options.retry !== undefined) { lines.push(`retry: ${options.retry}`); } // Handle multiline data by splitting and prefixing each line with "data: " const dataLines = options.data.split("\n"); for (const line of dataLines) { lines.push(`data: ${line}`); } return lines.join("\n") + "\n\n"; } // ============================================ // WebStreamWriter (Legacy Compatibility) // ============================================ /** * Base class for data stream writers * Provides common functionality for streaming data */ export class BaseDataStreamWriter { closed = false; closeHandlers = []; /** * Check if the stream is closed */ isClosed() { return this.closed; } /** * Register a close handler */ onClose(handler) { this.closeHandlers.push(handler); } /** * Close the stream */ close() { if (this.closed) { return; } this.closed = true; this.doClose(); for (const handler of this.closeHandlers) { try { handler(); } catch { // Ignore errors in close handlers } } } } /** * WebStreamWriter - Writes SSE events to a Web Streams API ReadableStream * * Provides a simple interface for creating streaming responses that can be * consumed by browsers and other HTTP clients. * * @example * ```typescript * const writer = new WebStreamWriter(); * * // Write data events * writer.writeData({ message: "Hello" }); * * // Write error events * writer.writeError("Something went wrong"); * * // Write done event and close * writer.writeDone(); * writer.close(); * * // Use the stream * return new Response(writer.stream, { * headers: { "Content-Type": "text/event-stream" } * }); * ``` */ export class WebStreamWriter extends BaseDataStreamWriter { controller = null; encoder = new TextEncoder(); /** The readable stream */ stream; constructor() { super(); this.stream = new ReadableStream({ start: (controller) => { this.controller = controller; }, cancel: () => { this.close(); }, }); } /** * Write raw text to the stream */ write(text) { if (!this.closed && this.controller) { this.controller.enqueue(this.encoder.encode(text)); } } /** * Write a data event */ writeData(data) { const event = formatSSEEvent({ event: "data", data: JSON.stringify(data), }); this.write(event); } /** * Write an error event */ writeError(message) { const event = formatSSEEvent({ event: "error", data: JSON.stringify({ error: message }), }); this.write(event); } /** * Write a done event */ writeDone() { const event = formatSSEEvent({ event: "done", data: JSON.stringify({ status: "done" }), }); this.write(event); } /** * Write a custom event */ writeEvent(eventType, data) { const event = formatSSEEvent({ event: eventType, data: JSON.stringify(data), }); this.write(event); } doClose() { if (this.controller) { try { this.controller.close(); } catch { // Ignore errors from already closed controller } this.controller = null; } } } //# sourceMappingURL=dataStream.js.map