dr / transformers /response-openai.js
hequ's picture
Upload 15 files
6dfd5ec verified
import { logDebug } from '../logger.js';
export class OpenAIResponseTransformer {
constructor(model, requestId) {
this.model = model;
this.requestId = requestId || `chatcmpl-${Date.now()}`;
this.created = Math.floor(Date.now() / 1000);
}
parseSSELine(line) {
if (line.startsWith('event:')) {
return { type: 'event', value: line.slice(6).trim() };
}
if (line.startsWith('data:')) {
const dataStr = line.slice(5).trim();
try {
return { type: 'data', value: JSON.parse(dataStr) };
} catch (e) {
return { type: 'data', value: dataStr };
}
}
return null;
}
transformEvent(eventType, eventData) {
logDebug(`Target OpenAI event: ${eventType}`);
if (eventType === 'response.created') {
return this.createOpenAIChunk('', 'assistant', false);
}
if (eventType === 'response.in_progress') {
return null;
}
if (eventType === 'response.output_text.delta') {
const text = eventData.delta || eventData.text || '';
return this.createOpenAIChunk(text, null, false);
}
if (eventType === 'response.output_text.done') {
return null;
}
if (eventType === 'response.done') {
const status = eventData.response?.status;
let finishReason = 'stop';
if (status === 'completed') {
finishReason = 'stop';
} else if (status === 'incomplete') {
finishReason = 'length';
}
const finalChunk = this.createOpenAIChunk('', null, true, finishReason);
const done = this.createDoneSignal();
return finalChunk + done;
}
return null;
}
createOpenAIChunk(content, role = null, finish = false, finishReason = null) {
const chunk = {
id: this.requestId,
object: 'chat.completion.chunk',
created: this.created,
model: this.model,
choices: [
{
index: 0,
delta: {},
finish_reason: finish ? finishReason : null
}
]
};
if (role) {
chunk.choices[0].delta.role = role;
}
if (content) {
chunk.choices[0].delta.content = content;
}
return `data: ${JSON.stringify(chunk)}\n\n`;
}
createDoneSignal() {
return 'data: [DONE]\n\n';
}
async *transformStream(sourceStream) {
let buffer = '';
let currentEvent = null;
try {
for await (const chunk of sourceStream) {
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
const parsed = this.parseSSELine(line);
if (!parsed) continue;
if (parsed.type === 'event') {
currentEvent = parsed.value;
} else if (parsed.type === 'data' && currentEvent) {
const transformed = this.transformEvent(currentEvent, parsed.value);
if (transformed) {
yield transformed;
}
}
}
}
if (currentEvent === 'response.done' || currentEvent === 'response.completed') {
yield this.createDoneSignal();
}
} catch (error) {
logDebug('Error in OpenAI stream transformation', error);
throw error;
}
}
}