@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
420 lines • 14.2 kB
JavaScript
/**
* 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