Files
OmniRoute/tests/unit/sse-parser.test.ts
2026-08-23 18:13:11 -03:00

434 lines
17 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
const { parseSSEToOpenAIResponse, parseSSEToClaudeResponse, parseSSEToResponsesOutput } =
await import("../../open-sse/handlers/sseParser.ts");
const { parseSSEToGeminiResponse } =
await import("../../open-sse/handlers/sseParser/geminiResponse.ts");
test("parseSSEToOpenAIResponse parses a single SSE event with a done marker", () => {
const rawSSE = [
'data: {"id":"chatcmpl_1","model":"gpt-4o-mini","choices":[{"index":0,"delta":{"content":"hello"},"finish_reason":"stop"}]}',
"",
"data: [DONE]",
"",
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.equal(parsed.id, "chatcmpl_1");
assert.equal(parsed.model, "gpt-4o-mini");
assert.equal(parsed.choices[0].message.content, "hello");
assert.equal(parsed.choices[0].finish_reason, "stop");
});
test("parseSSEToOpenAIResponse concatenates content, reasoning, and usage from multiple events", () => {
const rawSSE = [
'data: {"id":"chatcmpl_2","choices":[{"index":0,"delta":{"reasoning_content":"think "}}]}',
'data: {"id":"chatcmpl_2","choices":[{"index":0,"delta":{"content":"hel"}}]}',
'data: {"id":"chatcmpl_2","choices":[{"index":0,"delta":{"content":"lo"},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":2,"total_tokens":7}}',
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.equal(parsed.choices[0].message.content, "hello");
assert.equal(parsed.choices[0].message.reasoning_content, "think");
assert.deepEqual(parsed.usage, {
prompt_tokens: 5,
completion_tokens: 2,
total_tokens: 7,
});
});
test("parseSSEToOpenAIResponse ignores malformed and non-data lines", () => {
const rawSSE = [
"event: message",
"id: abc-1",
"not-data: ignored",
"data: not-json",
'data: {"id":"chatcmpl_3","choices":[{"index":0,"delta":{"content":"ok"},"finish_reason":"stop"}]}',
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.equal(parsed.id, "chatcmpl_3");
assert.equal(parsed.choices[0].message.content, "ok");
});
test("parseSSEToOpenAIResponse preserves UTF-8 multibyte content", () => {
const rawSSE = [
'data: {"id":"chatcmpl_utf8","choices":[{"index":0,"delta":{"content":"Olá "}}]}',
'data: {"id":"chatcmpl_utf8","choices":[{"index":0,"delta":{"content":"世界"},"finish_reason":"stop"}]}',
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.equal(parsed.choices[0].message.content, "Olá 世界");
});
test("parseSSEToOpenAIResponse ignores Responses API SSE payloads", () => {
const rawSSE = [
'data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.3-codex","status":"in_progress","output":[]}}',
'data: {"type":"response.output_text.delta","output_index":0,"delta":"Brasilia"}',
'data: {"type":"response.completed","response":{"id":"resp_1","object":"response","model":"gpt-5.3-codex","status":"completed","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Brasilia"}]}]}}',
"data: [DONE]",
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
assert.equal(parsed, null);
});
test("parseSSEToClaudeResponse parses text, thinking, tool_use, and usage events", () => {
const rawSSE = [
"event: message_start",
'data: {"type":"message_start","message":{"id":"msg_1","model":"claude-3-5-sonnet","role":"assistant","usage":{"input_tokens":10}}}',
"",
"event: content_block_start",
'data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"step 1","signature":"sig-1"}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":1,"delta":{"text":"Hello"}}',
"",
"event: content_block_start",
'data: {"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_1","name":"lookup","input":{}}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\\"q\\":\\"docs\\"}"}}',
"",
"event: message_delta",
'data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":"END"},"usage":{"output_tokens":4}}',
"",
].join("\n");
const parsed = parseSSEToClaudeResponse(rawSSE, "fallback-model");
assert.equal(parsed.id, "msg_1");
assert.equal(parsed.model, "claude-3-5-sonnet");
assert.equal((parsed.content[0] as { type: string }).type, "thinking");
assert.equal((parsed.content[0] as { thinking: string }).thinking, "step 1");
assert.equal((parsed.content[0] as { signature: string }).signature, "sig-1");
assert.equal((parsed.content[1] as { text: string }).text, "Hello");
assert.equal((parsed.content[2] as { type: string }).type, "tool_use");
assert.deepEqual((parsed.content[2] as { input: unknown }).input, { q: "docs" });
assert.equal(parsed.stop_reason, "tool_use");
assert.equal(parsed.stop_sequence, "END");
assert.deepEqual(parsed.usage, { input_tokens: 10, output_tokens: 4 });
});
test("parseSSEToClaudeResponse tolerates event-only types and missing blank separators", () => {
const rawSSE = [
"event: message_start",
'data: {"message":{"id":"msg_event_fallback","model":"claude-sonnet-4-6","role":"assistant","usage":{"input_tokens":3}}}',
"event: content_block_delta",
'data: {"index":0,"delta":{"text":"event fallback ok"}}',
"event: message_delta",
'data: {"delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":2}}',
"event: message_stop",
"data: {}",
].join("\n");
const parsed = parseSSEToClaudeResponse(rawSSE, "fallback-model");
assert.equal(parsed.id, "msg_event_fallback");
assert.equal(parsed.model, "claude-sonnet-4-6");
assert.equal((parsed.content[0] as { text: string }).text, "event fallback ok");
assert.deepEqual(parsed.usage, { input_tokens: 3, output_tokens: 2 });
});
test("parseSSEToClaudeResponse merges signature_delta into an existing thinking block", () => {
const rawSSE = [
"event: message_start",
'data: {"type":"message_start","message":{"id":"msg_thinking_sig","model":"claude-sonnet-4-6","role":"assistant"}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"first "}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"second"}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig-1"}}',
"",
].join("\n");
const parsed = parseSSEToClaudeResponse(rawSSE, "fallback-model");
assert.equal((parsed.content[0] as { type: string }).type, "thinking");
assert.equal((parsed.content[0] as { thinking: string }).thinking, "first second");
assert.equal((parsed.content[0] as { signature: string }).signature, "sig-1");
});
test("parseSSEToClaudeResponse preserves signature_delta when it arrives before thinking_delta", () => {
const rawSSE = [
"event: message_start",
'data: {"type":"message_start","message":{"id":"msg_sig_first","model":"claude-sonnet-4-6","role":"assistant"}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"sig-before"}}',
"",
"event: content_block_delta",
'data: {"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"later thinking"}}',
"",
].join("\n");
const parsed = parseSSEToClaudeResponse(rawSSE, "fallback-model");
assert.equal((parsed.content[0] as { type: string }).type, "thinking");
assert.equal((parsed.content[0] as { thinking: string }).thinking, "later thinking");
assert.equal((parsed.content[0] as { signature: string }).signature, "sig-before");
});
test("parseSSEToClaudeResponse ignores malformed payloads and returns null when nothing valid remains", () => {
const parsed = parseSSEToClaudeResponse(
["event: content_block_delta", "data: not-json", "", "data: [DONE]"].join("\n"),
"fallback-model"
);
assert.equal(parsed, null);
});
test("parseSSEToClaudeResponse ignores Responses API SSE payloads", () => {
const rawSSE = [
"event: response.created",
'data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-5.3-codex","status":"in_progress","output":[]}}',
"",
"event: response.output_text.delta",
'data: {"type":"response.output_text.delta","output_index":0,"delta":"Brasilia"}',
"",
"data: [DONE]",
].join("\n");
const parsed = parseSSEToClaudeResponse(rawSSE, "fallback-model");
assert.equal(parsed, null);
});
test("parseSSEToResponsesOutput prefers response.completed payloads when available", () => {
const rawSSE = [
'data: {"type":"response.created","response":{"id":"resp_1","model":"gpt-4.1","status":"in_progress","output":[]}}',
'data: {"type":"response.completed","response":{"id":"resp_1","model":"gpt-4.1","status":"completed","output":[{"type":"message"}],"usage":{"input_tokens":3}}}',
"data: [DONE]",
].join("\n");
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
assert.equal(parsed.id, "resp_1");
assert.equal(parsed.status, "completed");
assert.equal(parsed.output.length, 1);
assert.deepEqual(parsed.usage, { input_tokens: 3 });
});
test("parseSSEToResponsesOutput falls back to the latest response object when completion is absent", () => {
const rawSSE = [
'data: {"type":"response.in_progress","response":{"id":"resp_2","model":"gpt-4.1","status":"in_progress","output":[],"metadata":{"source":"sse"}}}',
].join("\n");
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
assert.equal(parsed.id, "resp_2");
assert.equal(parsed.model, "gpt-4.1");
assert.equal(parsed.status, "in_progress");
assert.deepEqual(parsed.metadata, { source: "sse" });
});
test("parseSSEToResponsesOutput handles large payloads without truncation", () => {
const largeText = "A".repeat(10_000);
const rawSSE = `data: ${JSON.stringify({
type: "response.completed",
response: {
id: "resp_big",
object: "response",
model: "gpt-4.1",
status: "completed",
output: [{ type: "message", content: [{ type: "output_text", text: largeText }] }],
},
})}`;
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
assert.equal(parsed.output[0].content[0].text.length, 10_000);
});
test("parseSSEToResponsesOutput treats response.cancelled as terminal and reconstructs output from deltas", () => {
const rawSSE = [
"event: response.created",
'data: {"type":"response.created","response":{"id":"resp_cancelled","model":"gpt-5.3-codex","status":"in_progress","output":[]}}',
"",
"event: response.output_item.added",
'data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_1","type":"message","role":"assistant","content":[{"type":"output_text","text":""}]}}',
"",
"event: response.output_text.delta",
'data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":"Hel"}',
"",
"event: response.output_text.delta",
'data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":"lo"}',
"",
"event: response.cancelled",
'data: {"type":"response.cancelled","response":{"id":"resp_cancelled","model":"gpt-5.3-codex","status":"cancelled","output":[],"usage":{"input_tokens":3}}}',
"",
"data: [DONE]",
].join("\n");
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
assert.equal(parsed.id, "resp_cancelled");
assert.equal(parsed.status, "cancelled");
assert.equal(parsed.output[0].type, "message");
assert.equal(parsed.output[0].content[0].text, "Hello");
assert.deepEqual(parsed.usage, { input_tokens: 3 });
});
test("parseSSEToResponsesOutput treats response.canceled as terminal and reconstructs message text without added item", () => {
const rawSSE = [
'data: {"type":"response.output_text.delta","output_index":0,"content_index":0,"delta":"Bye"}',
'data: {"type":"response.canceled","response":{"id":"resp_canceled","model":"gpt-5.3-codex","output":[]}}',
"data: [DONE]",
].join("\n");
const parsed = parseSSEToResponsesOutput(rawSSE, "fallback-model");
assert.equal(parsed.id, "resp_canceled");
assert.equal(parsed.status, "canceled");
assert.equal(parsed.output[0].type, "message");
assert.equal(parsed.output[0].content[0].text, "Bye");
});
test("parseSSEToOpenAIResponse deduplicates repeated tool call snapshots", () => {
const args = JSON.stringify({ command: "find /tmp -name test.txt" });
const first = {
id: "chatcmpl_tool",
choices: [
{
index: 0,
delta: {
tool_calls: [
{
index: 0,
id: "call_1",
type: "function",
function: { name: "shell", arguments: args },
},
],
},
},
],
};
const second = {
id: "chatcmpl_tool",
choices: [
{
index: 0,
delta: { tool_calls: [{ index: 0, function: { arguments: args } }] },
finish_reason: "tool_calls",
},
],
};
const rawSSE = [
`data: ${JSON.stringify(first)}`,
`data: ${JSON.stringify(second)}`,
"data: [DONE]",
].join("\n");
const parsed = parseSSEToOpenAIResponse(rawSSE, "fallback-model");
const toolCall = parsed.choices[0].message.tool_calls[0];
assert.equal(toolCall.function.arguments, args);
assert.equal(JSON.parse(toolCall.function.arguments).command, "find /tmp -name test.txt");
});
// ---------------------------------------------------------------------------
// parseSSEToGeminiResponse
// ---------------------------------------------------------------------------
test("parseSSEToGeminiResponse extracts text content from candidate parts", () => {
const rawSSE = [
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"Hello "}]}}]}}',
'data: {"response":{"candidates":[{"content":{"parts":[{"text":"world"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":3,"totalTokenCount":8}}}',
].join("\n");
const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-2.5-flash");
assert.ok(parsed);
assert.equal(parsed.object, "chat.completion");
assert.equal(parsed.choices[0].message.content, "Hello world");
assert.equal(parsed.choices[0].finish_reason, "stop");
assert.deepEqual(parsed.usage, {
prompt_tokens: 5,
completion_tokens: 3,
total_tokens: 8,
});
});
test("parseSSEToGeminiResponse handles markdown shortcut format", () => {
const rawSSE = [
'data: {"markdown":"Hello "}',
'data: {"markdown":"world"}',
'data: {"response":{"candidates":[{"finishReason":"STOP"}]}}',
].join("\n");
const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-2.5-flash");
assert.ok(parsed);
assert.equal(parsed.choices[0].message.content, "Hello world");
assert.equal(parsed.choices[0].finish_reason, "stop");
});
test("parseSSEToGeminiResponse extracts tool calls from textual format", () => {
const rawSSE = [
`data: ${JSON.stringify({
response: {
candidates: [
{
content: {
parts: [
{
text: '[Tool call: search_files]\nArguments: {"path":"/tmp"}',
},
],
},
finishReason: "STOP",
},
],
},
})}`,
].join("\n");
const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-3.7-flash-low");
assert.ok(parsed);
assert.equal(parsed.choices[0].finish_reason, "tool_calls");
const toolCalls = parsed.choices[0].message.tool_calls;
assert.equal(toolCalls.length, 1);
assert.equal(toolCalls[0].function.name, "search_files");
assert.deepEqual(JSON.parse(toolCalls[0].function.arguments), { path: "/tmp" });
});
test("parseSSEToGeminiResponse returns null for empty or non-content SSE", () => {
assert.equal(parseSSEToGeminiResponse("", "model"), null);
assert.equal(parseSSEToGeminiResponse("data: [DONE]\n", "model"), null);
assert.equal(parseSSEToGeminiResponse("event: ping\n", "model"), null);
});
test("parseSSEToGeminiResponse ignores thought/thoughtSignature parts", () => {
const rawSSE = [
`data: ${JSON.stringify({
response: {
candidates: [
{
content: {
parts: [{ text: "internal reasoning", thought: true }, { text: "visible answer" }],
},
finishReason: "STOP",
},
],
},
})}`,
].join("\n");
const parsed = parseSSEToGeminiResponse(rawSSE, "model");
assert.ok(parsed);
assert.equal(parsed.choices[0].message.content, "visible answer");
});