mirror of
https://github.com/Routstr/routstrd.git
synced 2026-07-30 15:46:14 +00:00
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { Transform } from "stream";
|
|
import type { UsageData } from "./types";
|
|
|
|
export function createSSEParserTransform(
|
|
onUsage: (usage: UsageData) => void,
|
|
onResponseId?: (responseId: string) => void,
|
|
): Transform {
|
|
let buffer = "";
|
|
|
|
const maybeCaptureUsageFromJson = (jsonText: string): void => {
|
|
try {
|
|
const data = JSON.parse(jsonText) as any;
|
|
const responseId = data.id;
|
|
if (typeof responseId === "string" && responseId.trim().length > 0) {
|
|
onResponseId?.(responseId.trim());
|
|
}
|
|
|
|
if (data.usage) {
|
|
const usageCost = data.usage.cost;
|
|
const cost =
|
|
typeof usageCost === "number"
|
|
? usageCost
|
|
: usageCost?.total_usd ??
|
|
data.metadata?.routstr?.cost?.total_usd ??
|
|
0;
|
|
const msats =
|
|
data.metadata?.routstr?.cost?.total_msats ??
|
|
(typeof data.usage.cost_sats === "number"
|
|
? data.usage.cost_sats * 1000
|
|
: 0);
|
|
onUsage({
|
|
promptTokens: data.usage.prompt_tokens ?? 0,
|
|
completionTokens: data.usage.completion_tokens ?? 0,
|
|
totalTokens: data.usage.total_tokens ?? 0,
|
|
cost,
|
|
satsCost: msats / 1000,
|
|
});
|
|
}
|
|
} catch {
|
|
// Ignore non-JSON lines/events.
|
|
}
|
|
};
|
|
|
|
const processLine = (self: Transform, line: string): void => {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) {
|
|
return;
|
|
}
|
|
|
|
if (trimmed === "data: [DONE]" || trimmed === "[DONE]") {
|
|
self.push("data: [DONE]\n\n");
|
|
return;
|
|
}
|
|
|
|
if (trimmed.startsWith("data:")) {
|
|
const dataStr = trimmed.startsWith("data: ")
|
|
? trimmed.slice(6)
|
|
: trimmed.slice(5).trimStart();
|
|
if (dataStr === "[DONE]") {
|
|
self.push("data: [DONE]\n\n");
|
|
return;
|
|
}
|
|
maybeCaptureUsageFromJson(dataStr);
|
|
self.push(`data: ${dataStr}\n\n`);
|
|
return;
|
|
}
|
|
|
|
if (trimmed.startsWith("{")) {
|
|
maybeCaptureUsageFromJson(trimmed);
|
|
self.push(`data: ${trimmed}\n\n`);
|
|
return;
|
|
}
|
|
|
|
self.push(line + "\n");
|
|
};
|
|
|
|
return new Transform({
|
|
transform(chunk, encoding, callback) {
|
|
buffer += chunk.toString();
|
|
|
|
const lines = buffer.split(/\r?\n/);
|
|
buffer = lines.pop() || "";
|
|
|
|
for (const line of lines) {
|
|
processLine(this, line);
|
|
}
|
|
|
|
callback();
|
|
},
|
|
flush(callback) {
|
|
if (buffer.trim()) {
|
|
processLine(this, buffer);
|
|
}
|
|
buffer = "";
|
|
callback();
|
|
},
|
|
});
|
|
}
|