Files
OmniRoute/tests/unit/logs-detail-partial-reasoning-chunk-split.test.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
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.
2026-08-23 11:45:01 -03:00

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: [] }), "");
});