File size: 5,367 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import test from "node:test";
import assert from "node:assert/strict";

const API_KEY = process.env.OMNIROUTE_API_KEY;
const BASE_URL = process.env.OMNIROUTE_URL || "http://localhost:20128";
const MODEL = process.env.TEST_GEMINI_MODEL || "default";

const skip = !API_KEY ? "OMNIROUTE_API_KEY not set — skipping live test" : undefined;

// Simple SSE reader (compatible with streamed chat completions)
async function readSSEStream(response: Response, onChunk?: (chunk: string) => void) {
  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  let buffer = "";
  let fullContent = "";
  let finishReason = "unknown";
  let totalTokens = 0;

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop() || "";
    for (const line of lines) {
      if (!line.startsWith("data: ")) continue;
      const data = line.slice(6).trim();
      if (data === "[DONE]") continue;
      try {
        const parsed = JSON.parse(data) as Record<string, unknown>;
        const choice = ((parsed?.choices ?? []) as Array<Record<string, unknown>>)[0];
        if (choice) {
          const delta = choice.delta as Record<string, unknown> | undefined;
          if (delta?.content) {
            let chunk = delta.content as string;
            fullContent += chunk;
            onChunk?.(chunk);
          }
          if (choice.finish_reason) finishReason = choice.finish_reason as string;
        }
        const usage = parsed.usage as Record<string, number> | undefined;
        if (usage) {
          totalTokens =
            usage.total_tokens ?? (usage.prompt_tokens ?? 0) + (usage.completion_tokens ?? 0);
        }
      } catch {
        // ignore malformed
      }
    }
  }

  return { fullContent, finishReason, totalTokens };
}

test("live request returns streamChunks", { skip }, async () => {
  console.log("[TEST] BASE_URL=", BASE_URL, "OMNIROUTE_URL=", process.env.OMNIROUTE_URL, "API_KEY set=", !!API_KEY);

  const messages = [
    { role: "system", content: "Execute the user prompt and provide a detailed explanation." },
    { role: "user", content: "Ackermann(4,3) step by step" },
  ];

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 60_000);

  try {
    const completionsResponse = await fetch(`${BASE_URL}/v1/chat/completions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({ model: MODEL, messages, stream: true, max_tokens: 512 }),
      signal: controller.signal,
    });

    assert.equal(
      completionsResponse.status,
      200,
      `expected 200 from chat/completions, got ${completionsResponse.status}`
    );

    const requestId = completionsResponse.headers.get("x-omniroute-request-id");
    assert.ok(requestId, "expected x-omniroute-request-id header in response");

    let streamFinished = false;

    let streamPromise = readSSEStream(completionsResponse).finally(() => {
      streamFinished = true;
    });

    const postPollStart = Date.now();
    const postPollTimeout = 60_000;

    let sawLogChunksWhileStreaming = false;

    while (Date.now() - postPollStart < postPollTimeout) {
      try {
        const activeRequestResponse = await fetch(
          `${BASE_URL}/api/logs/${encodeURIComponent(requestId!)}`,
          {
            headers: { Authorization: `Bearer ${API_KEY}` },
            cache: "no-store",
          }
        );
        if (activeRequestResponse.ok) {
          let activeRequest = await activeRequestResponse.json();
          if (activeRequest.active &&
              Array.isArray(activeRequest.pipelinePayloads.streamChunks.provider) &&
              activeRequest.pipelinePayloads.streamChunks.provider.length > 0
          ) {
            console.log("Stream chunks:", activeRequest.pipelinePayloads.streamChunks.provider);
            sawLogChunksWhileStreaming = true;
            break;
          }
        }
      } catch (e) {
        // ignore
      }

      if (streamFinished) {
        break;
      }

      await new Promise((r) => setTimeout(r, 250));
    }

    assert.ok(sawLogChunksWhileStreaming, "streamChunks never appeared while request was active");

    await streamPromise;
    const logDetailResponse = await fetch(
      `${BASE_URL}/api/logs/${encodeURIComponent(requestId!)}`,
      {
        headers: { Authorization: `Bearer ${API_KEY}` },
        cache: "no-store",
      }
    );
    assert.ok(logDetailResponse.ok, `failed to fetch log detail: ${logDetailResponse.status}`);
    let finishedRequest = await logDetailResponse.json();

    assert.equal(finishedRequest.id, requestId, "log detail id should match request id");
    assert.equal(finishedRequest.active, false, "request should be marked as inactive after completion");

    assert.ok(Array.isArray(finishedRequest.pipelinePayloads.streamChunks.provider) );
    assert.ok(Array.isArray(finishedRequest.pipelinePayloads.streamChunks.client));

    assert.ok(finishedRequest.pipelinePayloads.streamChunks.provider.length > 0);
    assert.ok(finishedRequest.pipelinePayloads.streamChunks.client.length > 0);
  } finally {
    clearTimeout(timeout);
  }
});