UNPKG

@xynehq/jaf

Version:

Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools

420 lines 14.2 kB
/** * JAF ADK Layer - Streaming System * * Functional streaming and live interaction utilities */ import { safeConsole } from '../../utils/logger.js'; // ========== Live Request Queue ========== const generateQueueId = () => { // Use crypto-based ID generation for pure functional approach return `queue_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; }; export const createLiveRequestQueue = () => { const id = generateQueueId(); let queueState = { items: [], closed: false }; return { id, enqueue: async (message) => { if (queueState.closed) { throw new Error('Queue is closed'); } // Create new state with immutable update queueState = { ...queueState, items: [...queueState.items, message] }; }, dequeue: async () => { if (queueState.items.length === 0) { return null; } const [first, ...rest] = queueState.items; // Create new state with immutable update queueState = { ...queueState, items: rest }; return first; }, isEmpty: () => { return queueState.items.length === 0; }, close: () => { queueState = { ...queueState, closed: true }; } }; }; // ========== Event Creation ========== export const createAgentEvent = (type, data) => ({ type, timestamp: new Date(), content: data?.content, functionCall: data?.functionCall, functionResponse: data?.functionResponse, error: data?.error, metadata: data?.metadata }); export const createMessageStartEvent = (content) => createAgentEvent('message_start', { content }); export const createMessageDeltaEvent = (content) => createAgentEvent('message_delta', { content }); export const createMessageCompleteEvent = (content) => createAgentEvent('message_complete', { content }); export const createFunctionCallStartEvent = (functionCall) => createAgentEvent('function_call_start', { functionCall }); export const createFunctionCallCompleteEvent = (functionResponse) => createAgentEvent('function_call_complete', { functionResponse }); export const createAgentTransferEvent = (targetAgent, metadata) => createAgentEvent('agent_transfer', { metadata: { ...metadata, targetAgent } }); export const createConversationEndEvent = (metadata) => createAgentEvent('conversation_end', { metadata }); export const createErrorEvent = (error, metadata) => createAgentEvent('error', { error, metadata }); // ========== Stream Utilities ========== export const streamToQueue = async (stream, queue) => { try { for await (const event of stream) { if (event.content) { await queue.enqueue(event.content); } } } finally { queue.close(); } }; export const queueToStream = async function* (queue) { // Read the queue's state to check if it's closed // For now, we'll process until the queue is empty while (!queue.isEmpty()) { const message = await queue.dequeue(); if (message) { yield createMessageDeltaEvent(message); } else { break; // Queue is empty } } }; export const combineStreams = async function* (...streams) { if (streams.length === 0) { return; } // Collect all events from all streams concurrently const streamPromises = streams.map(async (stream) => { const events = []; try { for await (const event of stream) { events.push(event); } } catch (error) { events.push(createErrorEvent(`Stream error: ${error instanceof Error ? error.message : String(error)}`)); } return events; }); try { const allStreamEvents = await Promise.all(streamPromises); // Flatten all events and yield them // This gives us all events from all streams, though not in real-time order for (const streamEvents of allStreamEvents) { for (const event of streamEvents) { yield event; } } } catch (error) { yield createErrorEvent(`Combined stream error: ${error instanceof Error ? error.message : String(error)}`); } }; export const filterEventStream = async function* (stream, predicate) { for await (const event of stream) { if (predicate(event)) { yield event; } } }; export const mapEventStream = async function* (stream, mapper) { for await (const event of stream) { yield mapper(event); } }; // ========== Stream Configuration ========== export const createStreamConfig = (responseModalities = ['TEXT'], options) => ({ responseModalities, bufferSize: options?.bufferSize || 1000, timeout: options?.timeout || 30000 }); export const createTextStreamConfig = () => createStreamConfig(['TEXT']); export const createAudioStreamConfig = () => createStreamConfig(['AUDIO']); export const createMultiModalStreamConfig = () => createStreamConfig(['TEXT', 'AUDIO', 'IMAGE']); // ========== Buffered Streaming ========== export const createBufferedStream = async function* (stream, bufferSize = 10) { let buffer = []; for await (const event of stream) { buffer.push(event); if (buffer.length >= bufferSize) { yield [...buffer]; buffer = []; } } // Yield remaining events if (buffer.length > 0) { yield buffer; } }; export const createThrottledStream = async function* (stream, intervalMs = 100) { let lastEmit = 0; for await (const event of stream) { const now = Date.now(); if (now - lastEmit >= intervalMs) { yield event; lastEmit = now; } else { // Wait for the remaining time const waitTime = intervalMs - (now - lastEmit); await new Promise(resolve => setTimeout(resolve, waitTime)); yield event; lastEmit = Date.now(); } } }; export const createDebouncedStream = async function* (stream, delayMs = 200) { let timeoutId = null; let pendingEvent = null; for await (const event of stream) { pendingEvent = event; if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { if (pendingEvent) { // This won't work directly in a generator // In a real implementation, you'd need a different approach // such as using a queue or event emitter } }, delayMs); } // Clean up if (timeoutId) { clearTimeout(timeoutId); } if (pendingEvent) { yield pendingEvent; } }; // ========== Event Processing ========== export const collectEvents = async (stream, predicate) => { const events = []; for await (const event of stream) { if (!predicate || predicate(event)) { events.push(event); } } return events; }; export const findFirstEvent = async (stream, predicate) => { for await (const event of stream) { if (predicate(event)) { return event; } } return null; }; export const waitForEvent = async (stream, type, timeout) => { if (!timeout) { // No timeout, iterate normally for await (const event of stream) { if (event.type === type) { return event; } } return null; } // Use Promise.race to handle timeout properly const timeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(null), timeout); }); const streamPromise = (async () => { for await (const event of stream) { if (event.type === type) { return event; } } return null; })(); return await Promise.race([streamPromise, timeoutPromise]); }; export const countEvents = async (stream, predicate) => { let count = 0; for await (const event of stream) { if (!predicate || predicate(event)) { count++; } } return count; }; // ========== Event Type Filters ========== export const isMessageEvent = (event) => ['message_start', 'message_delta', 'message_complete'].includes(event.type); export const isFunctionEvent = (event) => ['function_call_start', 'function_call_complete'].includes(event.type); export const isControlEvent = (event) => ['agent_transfer', 'conversation_end'].includes(event.type); export const isErrorEvent = (event) => event.type === 'error'; export const filterMessageEvents = (stream) => filterEventStream(stream, isMessageEvent); export const filterFunctionEvents = (stream) => filterEventStream(stream, isFunctionEvent); export const filterControlEvents = (stream) => filterEventStream(stream, isControlEvent); export const filterErrorEvents = (stream) => filterEventStream(stream, isErrorEvent); // ========== Stream Monitoring ========== export const monitorStream = async function* (stream, monitor) { for await (const event of stream) { try { monitor(event); } catch (error) { // Monitor errors shouldn't break the stream safeConsole.warn('Stream monitor error:', error); } yield event; } }; export const logStream = (prefix = 'STREAM') => (event) => { safeConsole.log(`[${prefix}] ${event.type}:`, { timestamp: event.timestamp, content: event.content ? 'present' : 'none', error: event.error, metadata: event.metadata }); }; export const metricsMonitor = () => { const metrics = { eventCount: 0, eventsByType: {}, errors: 0, startTime: Date.now(), firstEventTime: null, lastEventTime: null }; return { monitor: (event) => { const now = Date.now(); if (metrics.firstEventTime === null) { metrics.firstEventTime = now; } metrics.lastEventTime = now; metrics.eventCount++; metrics.eventsByType[event.type] = (metrics.eventsByType[event.type] || 0) + 1; if (event.type === 'error') { metrics.errors++; } }, getMetrics: () => { // Calculate duration based on actual event processing time if available const duration = metrics.firstEventTime && metrics.lastEventTime ? metrics.lastEventTime - metrics.firstEventTime : Date.now() - metrics.startTime; return { ...metrics, duration: Math.max(duration, 0), // Ensure non-negative duration eventsPerSecond: metrics.eventCount > 0 && duration > 0 ? metrics.eventCount / (duration / 1000) : 0 }; } }; }; // ========== Stream Error Handling ========== export const withStreamErrorHandling = async function* (stream, errorHandler) { try { for await (const event of stream) { yield event; } } catch (error) { const handledEvent = errorHandler ? errorHandler(error) : createErrorEvent(`Stream error: ${error.message}`); if (handledEvent) { yield handledEvent; } } }; export const retryStream = async function* (streamFactory, maxRetries = 3, delayMs = 1000) { let retries = 0; while (retries <= maxRetries) { try { const stream = streamFactory(); for await (const event of stream) { yield event; } return; // Success, exit } catch (error) { retries++; if (retries > maxRetries) { yield createErrorEvent(`Stream failed after ${maxRetries} retries: ${error.message}`); return; } yield createErrorEvent(`Stream error (retry ${retries}/${maxRetries}): ${error.message}`); // Wait before retry await new Promise(resolve => setTimeout(resolve, delayMs * retries)); } } }; export const createBidirectionalStream = () => { const inputQueue = createLiveRequestQueue(); const outputQueue = createLiveRequestQueue(); let closed = false; return { send: async (message) => { await inputQueue.enqueue(message); // For demo purposes, echo the message to the output queue await outputQueue.enqueue(message); }, receive: async function* () { while (!closed) { const message = await outputQueue.dequeue(); if (message) { yield createMessageDeltaEvent(message); } else if (outputQueue.isEmpty() && closed) { break; } else { await new Promise(resolve => setTimeout(resolve, 50)); } } }, close: () => { closed = true; inputQueue.close(); outputQueue.close(); } }; }; // ========== Stream Utilities ========== export const streamToArray = async (stream) => { const results = []; for await (const item of stream) { results.push(item); } return results; }; export const takeFromStream = async function* (stream, count) { let taken = 0; for await (const item of stream) { if (taken >= count) { break; } yield item; taken++; } }; export const skipFromStream = async function* (stream, count) { let skipped = 0; for await (const item of stream) { if (skipped < count) { skipped++; continue; } yield item; } }; //# sourceMappingURL=index.js.map