mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 03:42:21 +03:00
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host. Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean. Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
102 lines
4.6 KiB
TypeScript
102 lines
4.6 KiB
TypeScript
/**
|
|
* Regression test — the live "Generating… / Thinking…" preview in
|
|
* /api/logs/[id] read the request's in-flight stream-chunk log by parsing
|
|
* each logged chunk-array element independently. Each element is one raw
|
|
* network read (timestamp-prefixed for the debug display), not one complete
|
|
* SSE `data:` line, so a single JSON value (e.g. a `reasoning_content` delta)
|
|
* routinely splits across two or more elements. Parsing per-element in
|
|
* isolation intermittently fails JSON.parse and silently drops that piece,
|
|
* leaving gaps in the reconstructed text that read as garbled/scrambled
|
|
* reasoning once the survivors are concatenated — reported live via a
|
|
* dashboard screenshot showing exactly this on /dashboard/conversations.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { extractPartialAssistantText } = await import("../../src/app/api/logs/[id]/route.ts");
|
|
|
|
function chunkLine(timestamp: string, json: unknown): string {
|
|
return `[${timestamp}] data: ${JSON.stringify(json)}\n\n`;
|
|
}
|
|
|
|
test("extractPartialAssistantText: reasoning_content split across two chunk-log entries reassembles cleanly", () => {
|
|
const fullDelta = "Let me analyze this conversation carefully to create a checkpoint.";
|
|
// Simulate the exact real-world failure: a raw network read boundary lands
|
|
// mid-JSON-string, so the JSON text for one `reasoning_content` delta value
|
|
// is split across two separately-timestamped chunk-log array elements.
|
|
const splitPoint = 30;
|
|
const firstHalfJson = JSON.stringify({
|
|
choices: [{ delta: { reasoning_content: fullDelta.slice(0, splitPoint) } }],
|
|
});
|
|
const secondHalfJson = JSON.stringify({
|
|
choices: [{ delta: { reasoning_content: fullDelta.slice(splitPoint) } }],
|
|
});
|
|
const splitAt = firstHalfJson.indexOf(fullDelta.slice(0, splitPoint)) + splitPoint;
|
|
|
|
const chunkArr = [
|
|
`[23:55:00.100] data: ${firstHalfJson.slice(0, splitAt)}`,
|
|
`[23:55:00.101] ${firstHalfJson.slice(splitAt)}\n\n`,
|
|
chunkLine("23:55:00.102", { choices: [{ delta: { reasoning_content: "" } }] }),
|
|
];
|
|
// The second delta value is itself split too, to prove multi-split survives.
|
|
const secondSplitAt = 10;
|
|
chunkArr.push(`[23:55:00.103] data: ${secondHalfJson.slice(0, secondSplitAt)}`);
|
|
chunkArr.push(`[23:55:00.104] ${secondHalfJson.slice(secondSplitAt)}\n\n`);
|
|
|
|
const result = extractPartialAssistantText({ client: chunkArr });
|
|
|
|
assert.equal(
|
|
result,
|
|
`_Thinking…_\n\n${fullDelta}`,
|
|
"the reasoning text must reassemble whole, with no gaps from the split JSON values"
|
|
);
|
|
});
|
|
|
|
test("extractPartialAssistantText: without concatenation-first, the split would silently drop reasoning text (documents the bug this test guards against)", () => {
|
|
// Direct demonstration of the OLD (buggy) per-element parsing behavior, so
|
|
// this test file also documents exactly what broke: parsing each element
|
|
// in isolation, a fragment split mid-JSON-string is unparseable on its own.
|
|
const fullDelta = "some reasoning text";
|
|
const json = JSON.stringify({ choices: [{ delta: { reasoning_content: fullDelta } }] });
|
|
const splitAt = Math.floor(json.length / 2);
|
|
const first = `[00:00:00.000] data: ${json.slice(0, splitAt)}`;
|
|
const second = `[00:00:00.001] ${json.slice(splitAt)}`;
|
|
|
|
const oldBuggyParse = (raw: string): string | null => {
|
|
const idx = raw.indexOf("data:");
|
|
if (idx === -1) return null;
|
|
try {
|
|
JSON.parse(raw.slice(idx + 5).trim());
|
|
return "parsed";
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
assert.equal(oldBuggyParse(first), null, "first fragment alone is not valid JSON");
|
|
assert.equal(oldBuggyParse(second), null, "second fragment alone is not valid JSON either");
|
|
|
|
// But the fixed function, which concatenates before parsing, recovers it fully.
|
|
const result = extractPartialAssistantText({ client: [first, second] });
|
|
assert.equal(result, `_Thinking…_\n\n${fullDelta}`);
|
|
});
|
|
|
|
test("extractPartialAssistantText: content (not just reasoning) also survives a chunk-log split", () => {
|
|
const fullText = "The answer is forty-two.";
|
|
const json = JSON.stringify({ choices: [{ delta: { content: fullText } }] });
|
|
const splitAt = Math.floor(json.length / 2);
|
|
const chunkArr = [
|
|
`[10:00:00.000] data: ${json.slice(0, splitAt)}`,
|
|
`[10:00:00.001] ${json.slice(splitAt)}`,
|
|
];
|
|
|
|
const result = extractPartialAssistantText({ provider: chunkArr });
|
|
assert.equal(result, fullText);
|
|
});
|
|
|
|
test("extractPartialAssistantText: no reasoning/content anywhere returns empty string", () => {
|
|
assert.equal(extractPartialAssistantText(null), "");
|
|
assert.equal(extractPartialAssistantText({}), "");
|
|
assert.equal(extractPartialAssistantText({ client: [] }), "");
|
|
});
|