| import { translateResponse, initState } from "../translator/index.js"; |
| import { FORMATS } from "../translator/formats.js"; |
| import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb.js"; |
| import { extractUsage, hasValidUsage, estimateUsage, logUsage, addBufferToUsage, filterUsageForFormat, COLORS } from "./usageTracking.js"; |
| import { parseSSELine, hasValuableContent, fixInvalidId, formatSSE } from "./streamHelpers.js"; |
| import { getOpenAIResponsesEventName, isOpenAIResponsesTerminalEvent, formatIncompleteOpenAIResponsesStreamFailure } from "./responsesStreamHelpers.js"; |
| import { dbg, isDebugEnabled } from "./debugLog.js"; |
|
|
| import { SSE_DONE, SSE_HEADERS, SSE_HEADERS_NO_BUFFER } from "./sseConstants.js"; |
|
|
| export { COLORS, formatSSE }; |
| export { SSE_DONE, SSE_HEADERS, SSE_HEADERS_NO_BUFFER }; |
|
|
| |
| const sharedEncoder = new TextEncoder(); |
|
|
| |
| |
| |
| const STREAM_MODE = { |
| TRANSLATE: "translate", |
| PASSTHROUGH: "passthrough" |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function createSSEStream(options = {}) { |
| const { |
| mode = STREAM_MODE.TRANSLATE, |
| targetFormat, |
| sourceFormat, |
| provider = null, |
| reqLogger = null, |
| toolNameMap = null, |
| model = null, |
| connectionId = null, |
| body = null, |
| onStreamComplete = null, |
| apiKey = null |
| } = options; |
|
|
| let buffer = ""; |
| let usage = null; |
|
|
| |
| const decoder = new TextDecoder("utf-8", { fatal: false }); |
|
|
| const state = mode === STREAM_MODE.TRANSLATE ? { ...initState(sourceFormat), provider, toolNameMap, model } : null; |
|
|
| let totalContentLength = 0; |
| let accumulatedContent = ""; |
| let accumulatedThinking = ""; |
| let ttftAt = null; |
| let sseLineCount = 0; |
| let sseEmittedCount = 0; |
| const eventTypeCounts = {}; |
|
|
| |
| let currentOpenAIResponsesEvent = null; |
| let openAIResponsesTerminalSeen = false; |
| let openAIResponsesDoneSent = false; |
|
|
| return new TransformStream({ |
| transform(chunk, controller) { |
| if (!ttftAt) ttftAt = Date.now(); |
| const text = decoder.decode(chunk, { stream: true }); |
| buffer += text; |
| reqLogger?.appendProviderChunk?.(text); |
|
|
| const lines = buffer.split("\n"); |
| buffer = lines.pop() || ""; |
|
|
| for (const line of lines) { |
| const trimmed = line.trim(); |
| if (isDebugEnabled && trimmed) { |
| sseLineCount++; |
| if (trimmed.startsWith("event:")) { |
| const evt = trimmed.slice(6).trim(); |
| eventTypeCounts[evt] = (eventTypeCounts[evt] || 0) + 1; |
| } |
| } |
|
|
| |
| if (mode === STREAM_MODE.TRANSLATE && targetFormat === FORMATS.OPENAI_RESPONSES && trimmed.startsWith("event:")) { |
| currentOpenAIResponsesEvent = trimmed.slice(6).trim(); |
| } |
|
|
| |
| if (mode === STREAM_MODE.PASSTHROUGH) { |
| let output; |
| let injectedUsage = false; |
|
|
| if (trimmed.startsWith("data:") && trimmed.slice(5).trim() !== "[DONE]") { |
| try { |
| const parsed = JSON.parse(trimmed.slice(5).trim()); |
|
|
| const idFixed = fixInvalidId(parsed); |
|
|
| |
| let fieldsInjected = false; |
| if (parsed.choices !== undefined) { |
| if (!parsed.object) { parsed.object = "chat.completion.chunk"; fieldsInjected = true; } |
| if (!parsed.created) { parsed.created = Math.floor(Date.now() / 1000); fieldsInjected = true; } |
| } |
|
|
| |
| if (parsed.prompt_filter_results !== undefined) { |
| delete parsed.prompt_filter_results; |
| fieldsInjected = true; |
| } |
| if (parsed?.choices) { |
| for (const choice of parsed.choices) { |
| if (choice.content_filter_results !== undefined) { |
| delete choice.content_filter_results; |
| fieldsInjected = true; |
| } |
| } |
| } |
|
|
| if (!hasValuableContent(parsed, FORMATS.OPENAI)) { |
| continue; |
| } |
|
|
| const delta = parsed.choices?.[0]?.delta; |
| const content = delta?.content; |
| const reasoning = delta?.reasoning_content; |
| if (content && typeof content === "string") { |
| totalContentLength += content.length; |
| accumulatedContent += content; |
| } |
| if (reasoning && typeof reasoning === "string") { |
| totalContentLength += reasoning.length; |
| accumulatedThinking += reasoning; |
| } |
|
|
| const extracted = extractUsage(parsed); |
| if (extracted) { |
| usage = extracted; |
| } |
|
|
| const isFinishChunk = parsed.choices?.[0]?.finish_reason; |
| if (isFinishChunk && !hasValidUsage(parsed.usage)) { |
| const estimated = estimateUsage(body, totalContentLength, FORMATS.OPENAI); |
| parsed.usage = filterUsageForFormat(estimated, FORMATS.OPENAI); |
| output = `data: ${JSON.stringify(parsed)}\n`; |
| usage = estimated; |
| injectedUsage = true; |
| } else if (isFinishChunk && usage) { |
| const buffered = addBufferToUsage(usage); |
| parsed.usage = filterUsageForFormat(buffered, FORMATS.OPENAI); |
| output = `data: ${JSON.stringify(parsed)}\n`; |
| injectedUsage = true; |
| } else if (idFixed || fieldsInjected) { |
| output = `data: ${JSON.stringify(parsed)}\n`; |
| injectedUsage = true; |
| } |
| } catch { } |
| } |
|
|
| if (!injectedUsage) { |
| if (line.startsWith("data:") && !line.startsWith("data: ")) { |
| output = "data: " + line.slice(5) + "\n"; |
| } else { |
| output = line + "\n"; |
| } |
| } |
|
|
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| continue; |
| } |
|
|
| |
| if (!trimmed) continue; |
|
|
| const parsed = parseSSELine(trimmed, targetFormat); |
| if (!parsed) continue; |
|
|
| |
| const isOpenAIResponsesStream = targetFormat === FORMATS.OPENAI_RESPONSES; |
| const keepsOpenAIResponsesFormat = isOpenAIResponsesStream && sourceFormat === FORMATS.OPENAI_RESPONSES; |
| const openAIResponsesEventName = isOpenAIResponsesStream |
| ? getOpenAIResponsesEventName(currentOpenAIResponsesEvent, parsed) |
| : null; |
|
|
| if (isOpenAIResponsesStream && isOpenAIResponsesTerminalEvent(openAIResponsesEventName, parsed)) { |
| openAIResponsesTerminalSeen = true; |
| } |
|
|
| |
| |
| if (parsed && parsed.done && targetFormat !== FORMATS.OLLAMA) { |
| |
| if (keepsOpenAIResponsesFormat && !openAIResponsesTerminalSeen) { |
| const failedOutput = formatIncompleteOpenAIResponsesStreamFailure(); |
| reqLogger?.appendConvertedChunk?.(failedOutput); |
| controller.enqueue(sharedEncoder.encode(failedOutput)); |
| openAIResponsesTerminalSeen = true; |
| sseEmittedCount++; |
| } |
|
|
| const output = "data: [DONE]\n\n"; |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| if (keepsOpenAIResponsesFormat) openAIResponsesDoneSent = true; |
| continue; |
| } |
|
|
| |
| if (parsed.delta?.text) { |
| totalContentLength += parsed.delta.text.length; |
| accumulatedContent += parsed.delta.text; |
| } |
| |
| if (parsed.delta?.thinking) { |
| totalContentLength += parsed.delta.thinking.length; |
| accumulatedThinking += parsed.delta.thinking; |
| } |
| |
| |
| if (parsed.choices?.[0]?.delta?.content) { |
| totalContentLength += parsed.choices[0].delta.content.length; |
| accumulatedContent += parsed.choices[0].delta.content; |
| } |
| |
| if (parsed.choices?.[0]?.delta?.reasoning_content) { |
| totalContentLength += parsed.choices[0].delta.reasoning_content.length; |
| accumulatedThinking += parsed.choices[0].delta.reasoning_content; |
| } |
| |
| |
| if (parsed.candidates?.[0]?.content?.parts) { |
| for (const part of parsed.candidates[0].content.parts) { |
| if (part.text && typeof part.text === "string") { |
| totalContentLength += part.text.length; |
| |
| if (part.thought === true) { |
| accumulatedThinking += part.text; |
| } else { |
| accumulatedContent += part.text; |
| } |
| } |
| } |
| } |
|
|
| |
| const extracted = extractUsage(parsed); |
| if (extracted) state.usage = extracted; |
|
|
| |
| if (keepsOpenAIResponsesFormat && openAIResponsesEventName) { |
| const output = formatSSE({ event: openAIResponsesEventName, data: parsed }, sourceFormat); |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| currentOpenAIResponsesEvent = null; |
| sseEmittedCount++; |
| continue; |
| } |
|
|
| currentOpenAIResponsesEvent = null; |
|
|
| |
| const translated = translateResponse(targetFormat, sourceFormat, parsed, state); |
|
|
| |
| if (translated?._openaiIntermediate) { |
| for (const item of translated._openaiIntermediate) { |
| const openaiOutput = formatSSE(item, FORMATS.OPENAI); |
| reqLogger?.appendOpenAIChunk?.(openaiOutput); |
| } |
| } |
|
|
| if (translated?.length > 0) { |
| for (const item of translated) { |
| if (item === null || item === undefined) continue; |
| |
| if (!hasValuableContent(item, sourceFormat)) { |
| continue; |
| } |
|
|
| |
| const isFinishChunk = item.type === "message_delta" || item.choices?.[0]?.finish_reason; |
| if (state.finishReason && isFinishChunk && !hasValidUsage(item.usage) && totalContentLength > 0) { |
| const estimated = estimateUsage(body, totalContentLength, sourceFormat); |
| item.usage = filterUsageForFormat(estimated, sourceFormat); |
| state.usage = estimated; |
| } else if (state.finishReason && isFinishChunk && state.usage) { |
| |
| const buffered = addBufferToUsage(state.usage); |
| item.usage = filterUsageForFormat(buffered, sourceFormat); |
| } |
|
|
| const output = formatSSE(item, sourceFormat); |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| sseEmittedCount++; |
| } |
| } |
| } |
| }, |
|
|
| flush(controller) { |
| const evtSummary = Object.entries(eventTypeCounts).map(([k, v]) => `${k}=${v}`).join(",") || "none"; |
| dbg("SSE", `flush | provider=${provider} | model=${model} | recvLines=${sseLineCount} | emitted=${sseEmittedCount} | events=[${evtSummary}]`); |
| trackPendingRequest(model, provider, connectionId, false); |
| try { |
| const remaining = decoder.decode(); |
| if (remaining) buffer += remaining; |
|
|
| if (mode === STREAM_MODE.PASSTHROUGH) { |
| if (buffer) { |
| let output = buffer; |
| if (buffer.startsWith("data:") && !buffer.startsWith("data: ")) { |
| output = "data: " + buffer.slice(5); |
| } |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| } |
|
|
| if (!hasValidUsage(usage) && totalContentLength > 0) { |
| usage = estimateUsage(body, totalContentLength, FORMATS.OPENAI); |
| } |
|
|
| if (hasValidUsage(usage)) { |
| logUsage(provider, usage, model, connectionId, apiKey); |
| } else { |
| appendRequestLog({ model, provider, connectionId, tokens: null, status: "200 OK" }).catch(() => { }); |
| } |
| |
| |
| |
| |
| |
| const doneOutput = "data: [DONE]\n\n"; |
| reqLogger?.appendConvertedChunk?.(doneOutput); |
| controller.enqueue(sharedEncoder.encode(doneOutput)); |
|
|
| if (onStreamComplete) { |
| onStreamComplete({ |
| content: accumulatedContent, |
| thinking: accumulatedThinking |
| }, usage, ttftAt); |
| } |
| return; |
| } |
|
|
| if (buffer.trim()) { |
| const parsed = parseSSELine(buffer.trim()); |
| if (parsed && !parsed.done) { |
| const translated = translateResponse(targetFormat, sourceFormat, parsed, state); |
|
|
| if (translated?._openaiIntermediate) { |
| for (const item of translated._openaiIntermediate) { |
| const openaiOutput = formatSSE(item, FORMATS.OPENAI); |
| reqLogger?.appendOpenAIChunk?.(openaiOutput); |
| } |
| } |
|
|
| if (translated?.length > 0) { |
| for (const item of translated) { |
| if (item === null || item === undefined) continue; |
| const output = formatSSE(item, sourceFormat); |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| } |
| } |
| } |
| } |
|
|
| const flushed = translateResponse(targetFormat, sourceFormat, null, state); |
|
|
| if (flushed?._openaiIntermediate) { |
| for (const item of flushed._openaiIntermediate) { |
| const openaiOutput = formatSSE(item, FORMATS.OPENAI); |
| reqLogger?.appendOpenAIChunk?.(openaiOutput); |
| } |
| } |
|
|
| if (flushed?.length > 0) { |
| for (const item of flushed) { |
| if (item === null || item === undefined) continue; |
| const output = formatSSE(item, sourceFormat); |
| reqLogger?.appendConvertedChunk?.(output); |
| controller.enqueue(sharedEncoder.encode(output)); |
| } |
| } |
|
|
| |
| const keepsOpenAIResponsesFormat = targetFormat === FORMATS.OPENAI_RESPONSES && sourceFormat === FORMATS.OPENAI_RESPONSES; |
| if (keepsOpenAIResponsesFormat && !openAIResponsesTerminalSeen) { |
| const failedOutput = formatIncompleteOpenAIResponsesStreamFailure(); |
| reqLogger?.appendConvertedChunk?.(failedOutput); |
| controller.enqueue(sharedEncoder.encode(failedOutput)); |
| openAIResponsesTerminalSeen = true; |
| } |
|
|
| if (!keepsOpenAIResponsesFormat || !openAIResponsesDoneSent) { |
| const doneOutput = "data: [DONE]\n\n"; |
| reqLogger?.appendConvertedChunk?.(doneOutput); |
| controller.enqueue(sharedEncoder.encode(doneOutput)); |
| } |
|
|
| if (!hasValidUsage(state?.usage) && totalContentLength > 0) { |
| state.usage = estimateUsage(body, totalContentLength, sourceFormat); |
| } |
|
|
| if (hasValidUsage(state?.usage)) { |
| logUsage(state.provider || targetFormat, state.usage, model, connectionId, apiKey); |
| } else { |
| appendRequestLog({ model, provider, connectionId, tokens: null, status: "200 OK" }).catch(() => { }); |
| } |
| |
| if (onStreamComplete) { |
| onStreamComplete({ |
| content: accumulatedContent, |
| thinking: accumulatedThinking |
| }, state?.usage, ttftAt); |
| } |
| } catch (error) { |
| console.log("Error in flush:", error); |
| } |
| } |
| }); |
| } |
|
|
| export function createSSETransformStreamWithLogger(targetFormat, sourceFormat, provider = null, reqLogger = null, toolNameMap = null, model = null, connectionId = null, body = null, onStreamComplete = null, apiKey = null) { |
| return createSSEStream({ |
| mode: STREAM_MODE.TRANSLATE, |
| targetFormat, |
| sourceFormat, |
| provider, |
| reqLogger, |
| toolNameMap, |
| model, |
| connectionId, |
| body, |
| onStreamComplete, |
| apiKey |
| }); |
| } |
|
|
| export function createPassthroughStreamWithLogger(provider = null, reqLogger = null, model = null, connectionId = null, body = null, onStreamComplete = null, apiKey = null) { |
| return createSSEStream({ |
| mode: STREAM_MODE.PASSTHROUGH, |
| provider, |
| reqLogger, |
| model, |
| connectionId, |
| body, |
| onStreamComplete, |
| apiKey |
| }); |
| } |
|
|