@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
JavaScript
/**
* 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