UNPKG

@copilotkit/runtime

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

203 lines (176 loc) • 6.03 kB
import { ReplaySubject } from "rxjs"; import { CopilotKitLowLevelError, CopilotKitError, CopilotKitErrorCode, ensureStructuredError, } from "@copilotkit/shared"; import { errorConfig, getFallbackMessage } from "./error-messages"; export async function writeJsonLineResponseToEventStream<T>( response: ReadableStream<Uint8Array>, eventStream$: ReplaySubject<T>, ) { const reader = response.getReader(); const decoder = new TextDecoder(); let buffer = []; function flushBuffer() { const currentBuffer = buffer.join(""); if (currentBuffer.trim().length === 0) { return; } const parts = currentBuffer.split("\n"); if (parts.length === 0) { return; } const lastPartIsComplete = currentBuffer.endsWith("\n"); // truncate buffer buffer = []; if (!lastPartIsComplete) { // put back the last part buffer.push(parts.pop()); } parts .map((part) => part.trim()) .filter((part) => part != "") .forEach((part) => { eventStream$.next(JSON.parse(part)); }); } try { while (true) { const { done, value } = await reader.read(); if (!done) { buffer.push(decoder.decode(value, { stream: true })); } flushBuffer(); if (done) { break; } } } catch (error) { // Preserve already structured CopilotKit errors, only convert unstructured errors const structuredError = ensureStructuredError(error, convertStreamingErrorToStructured); eventStream$.error(structuredError); return; } eventStream$.complete(); } function convertStreamingErrorToStructured(error: any): CopilotKitError { // Determine a more helpful error message based on context let helpfulMessage = generateHelpfulErrorMessage(error); // For network-related errors, use CopilotKitLowLevelError to preserve the original error if ( error?.message?.includes("fetch failed") || error?.message?.includes("ECONNREFUSED") || error?.message?.includes("ENOTFOUND") || error?.message?.includes("ETIMEDOUT") || error?.message?.includes("terminated") || error?.cause?.code === "UND_ERR_SOCKET" || error?.message?.includes("other side closed") || error?.code === "UND_ERR_SOCKET" ) { console.log("error", error); return new CopilotKitLowLevelError({ error: error instanceof Error ? error : new Error(String(error)), url: "streaming connection", message: helpfulMessage, }); } // For all other errors, preserve the raw error in a basic CopilotKitError return new CopilotKitError({ message: helpfulMessage, code: CopilotKitErrorCode.UNKNOWN, }); } /** * Generates a helpful error message based on error patterns and context */ export function generateHelpfulErrorMessage(error: any, context: string = "connection"): string { const baseMessage = error?.message || String(error); // Check for preserved error information from Python agent const originalErrorType = error?.originalErrorType || error?.extensions?.originalErrorType; const statusCode = error?.statusCode || error?.extensions?.statusCode; const responseData = error?.responseData || error?.extensions?.responseData; // First, try to match by original error type if available (more specific) if (originalErrorType) { const typeConfig = errorConfig.errorPatterns[originalErrorType]; if (typeConfig) { return typeConfig.message.replace("{context}", context); } } // Check for specific error patterns from configuration for (const [pattern, config] of Object.entries(errorConfig.errorPatterns)) { const shouldMatch = baseMessage?.includes(pattern) || error?.cause?.code === pattern || error?.code === pattern || statusCode === parseInt(pattern) || (pattern === "other_side_closed" && baseMessage?.includes("other side closed")) || (pattern === "fetch_failed" && baseMessage?.includes("fetch failed")) || (responseData && JSON.stringify(responseData).includes(pattern)); if (shouldMatch) { // Replace {context} placeholder with actual context return config.message.replace("{context}", context); } } // Try to match by category for fallback messages if (isNetworkError(error)) { return getFallbackMessage("network"); } if (isConnectionError(error)) { return getFallbackMessage("connection"); } if (isAuthenticationError(error)) { return getFallbackMessage("authentication"); } // Default fallback return getFallbackMessage("default"); } /** * Determines if an error is network-related */ function isNetworkError(error: any): boolean { const networkPatterns = ["ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "fetch_failed"]; return networkPatterns.some( (pattern) => error?.message?.includes(pattern) || error?.cause?.code === pattern || error?.code === pattern, ); } /** * Determines if an error is connection-related */ function isConnectionError(error: any): boolean { const connectionPatterns = ["terminated", "UND_ERR_SOCKET", "other side closed"]; return connectionPatterns.some( (pattern) => error?.message?.includes(pattern) || error?.cause?.code === pattern || error?.code === pattern, ); } /** * Determines if an error is authentication-related */ function isAuthenticationError(error: any): boolean { const authPatterns = [ "401", "api key", "unauthorized", "authentication", "AuthenticationError", "PermissionDeniedError", ]; const baseMessage = error?.message || String(error); const originalErrorType = error?.originalErrorType || error?.extensions?.originalErrorType; const statusCode = error?.statusCode || error?.extensions?.statusCode; return authPatterns.some( (pattern) => baseMessage?.toLowerCase().includes(pattern.toLowerCase()) || originalErrorType === pattern || statusCode === 401 || error?.status === 401 || error?.statusCode === 401, ); }