// SPDX-License-Identifier: AGPL-3.0-only // Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 /** * Two guards on `code-plugin.ts` that nothing else covers. * * 1. The regex engine. The desktop app's CSP is `default-src 'self'` with no * `wasm-unsafe-eval` (studio/src-tauri/tauri.conf.json), and the packaged * frontend is the same bundle the browser gets. Shiki's default Oniguruma * engine is WebAssembly, so swapping to it would work in every browser and * every test here, and be blocked at runtime in the packaged desktop app. * No CI job builds the desktop bundle and drives its webview, so this is * the only thing standing between that change and a shipped regression. * * 2. The cache budget. `MAX_CACHED_CHARACTERS` is 512,000 and the largest * fixture in the sibling files is ~36 KB, so the character-budget branch, * its "never evict the only fence" carve-out, and the running character * count were all unreachable. A miscount there degrades silently: either * the cache stops evicting and grows without bound, or it evicts fences it * should have kept and quietly reverts to full re-tokenization. */ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import type { HighlightOptions, HighlightResult, ThemeInput, } from "@streamdown/code"; import { createHighlighter } from "shiki"; import { createJavaScriptRegexEngine } from "shiki/engine/javascript"; import { createCodePlugin } from "../src/components/assistant-ui/code-plugin.ts"; const THEMES: [ThemeInput, ThemeInput] = ["github-light", "github-dark"]; const PLUGIN_SOURCE = readFileSync( new URL("../src/components/assistant-ui/code-plugin.ts", import.meta.url), "utf8", ); const highlightOnce = ( plugin: ReturnType, options: HighlightOptions, ): Promise => new Promise((resolve) => { const immediate = plugin.highlight(options, resolve); if (immediate) resolve(immediate); }); // ── 1. the engine the desktop CSP allows ─────────────────────────────── test("the highlighter uses the JavaScript regex engine, not the WASM one", () => { // Assert on a boolean, not on PLUGIN_SOURCE: assert.match prints the whole // subject, so matching the file directly buries the message under ~10 KB. assert.ok( /createJavaScriptRegexEngine/.test(PLUGIN_SOURCE), "code-plugin must build its highlighter with shiki's JavaScript regex engine", ); assert.ok( !/shiki\/wasm|loadWasm|createOnigurumaEngine/.test(PLUGIN_SOURCE), // This is a source-text assertion, not a behavioural one: it pins the name, // so an alias would trip it and a same-named wrapper would slip past. That // is the accepted cost of covering a constraint no runtime here can check, // since nothing in CI drives the packaged webview. "shiki's Oniguruma engine is WebAssembly, which the desktop app's CSP " + "(default-src 'self', no wasm-unsafe-eval) blocks; the packaged app ships " + "this same bundle and no CI job would catch it", ); }); // ── 2. the character budget ──────────────────────────────────────────── /** Distinct, cheap-to-tokenize source of roughly `chars` characters. */ const sourceOfSize = (id: number, chars: number): string => { const lines: string[] = []; let length = 0; for (let i = 0; length < chars; i += 1) { const line = `const value_${id}_${i} = ${i};`; lines.push(line); length += line.length + 1; } return `${lines.join("\n")}\n`; }; /** * Whether the plugin still holds the entry that produced `previous`. * * Re-requesting is not enough on its own: a miss re-tokenizes and re-caches, so * asking twice always agrees with itself. The only signal is whether the entry * from before is still the one being served, so compare against the object the * cache handed out earlier. */ const stillCached = async ( plugin: ReturnType, previous: HighlightResult, code: string, language: HighlightOptions["language"], ): Promise => (await highlightOnce(plugin, { code, language, themes: THEMES })) === previous; test("the character budget evicts even while the fence count is under its limit", async () => { // 100 fences of ~6 KB is 600 KB, past MAX_CACHED_CHARACTERS, while staying // well under MAX_FENCES. Only the character branch can evict here, so this // fails outright if that branch is unreachable or miscounted. const plugin = createCodePlugin({ themes: THEMES }); const first = sourceOfSize(0, 6_000); const held = await highlightOnce(plugin, { code: first, language: "typescript", themes: THEMES, }); assert.equal( await stillCached(plugin, held, first, "typescript"), true, "the fence was not cached to begin with, so the check below proves nothing", ); for (let id = 1; id < 100; id += 1) { await highlightOnce(plugin, { code: sourceOfSize(id, 6_000), language: "typescript", themes: THEMES, }); } assert.equal( await stillCached(plugin, held, first, "typescript"), false, "600 KB of fences did not evict the oldest; the character budget is not being enforced", ); }); test("a fence larger than the whole budget is kept rather than evicting itself", async () => { // The carve-out at the eviction loop: stop when one fence is left, so a // single oversized fence is retained. Without it the only fence in the cache // would be dropped the moment it exceeded the budget, and every refresh of a // long block would re-tokenize from zero, which is the case this PR exists // to remove. const plugin = createCodePlugin({ themes: THEMES }); const huge = sourceOfSize(1, 600_000); assert.ok(huge.length > 512_000, "fixture must exceed MAX_CACHED_CHARACTERS"); const held = await highlightOnce(plugin, { code: huge, language: "typescript", themes: THEMES, }); assert.equal( await stillCached(plugin, held, huge, "typescript"), true, "the only fence in the cache was evicted for being over budget", ); }); test("a fence evicted mid-stream still tokenizes correctly when it resumes", async () => { // Eviction drops the committed lines and the grammar state, so the fence // restarts from offset 0 and rebuilds incremental state as it keeps growing. // That is a performance loss by design; it must not be a correctness one. // // Two things this test needs that are easy to get wrong, both found by // measurement rather than assumed: // // - It must wait out REFRESH_MS between updates. Past MIN_INCREMENTAL_CHARS, // two updates inside that window return the throttled approximation, which // renders the uncommitted tail plain and never reads the grammar state. // Without the wait, 22 of 23 comparisons took that path and the test could // not fail however the resume was broken. // - The fixture must be one whose tokens actually change when the resumed // state is dropped. A python triple-quoted string is not: dropping the // state left its tokens byte-identical. HTML with an embedded script is, // because the grammar is several levels deep at the resume point. const chunk = [ "
", '
text
', " ", " ", "
", ].join("\n"); let source = "\n\n\n"; for (let i = 0; i < 12; i += 1) source += `${chunk}\n`; source += "\n\n"; assert.ok(source.length > 4_000, "fixture must clear MIN_INCREMENTAL_CHARS"); const highlighter = await createHighlighter({ themes: THEMES, langs: ["html"], engine: createJavaScriptRegexEngine({ forgiving: true }), }); const oracle = (code: string) => highlighter.codeToTokens(code, { lang: "html", themes: { light: "github-light", dark: "github-dark" }, }).tokens; const settle = () => new Promise((r) => setTimeout(r, 260)); const plugin = createCodePlugin({ themes: THEMES }); const half = Math.floor(source.length / 2); // Grow past the incremental threshold so committed state exists to lose. for (let length = 2_100; length <= half; length += 700) { await settle(); await highlightOnce(plugin, { code: source.slice(0, length), language: "html", themes: THEMES, }); } // Evict it. for (let id = 0; id < 100; id += 1) { await highlightOnce(plugin, { code: sourceOfSize(id, 6_000), language: "html", themes: THEMES, }); } // Keep streaming. Every prefix must still match the whole document. for (let length = half; length <= source.length; length += 700) { await settle(); const code = source.slice(0, length); const streamed = await highlightOnce(plugin, { code, language: "html", themes: THEMES, }); assert.deepEqual( streamed.tokens, oracle(code), `a fence that lost its cache mid-stream diverged at ${length} of ${source.length}`, ); } await settle(); const final = await highlightOnce(plugin, { code: source, language: "html", themes: THEMES, }); assert.deepEqual(final.tokens, oracle(source)); }); // ── 3. the compact cache key is a hint, not the answer ───────────────── /** * `codeKey` hashes a fence down to `key + length + first 32 + last 32` so an * update does not rehash the whole block. That is a lossy digest, so two * different fences can share one. `findFence` therefore treats a hit as a * candidate and confirms it with `exact.code === code` before serving it. * * Drop that confirmation and the second fence is served the first one's * tokens: the reader sees the wrong code, with no error anywhere. The upstream * @streamdown/code cache this file replaces keys on the same shape (length * plus the first and last 100 characters) and does not confirm, so the pair * below is rendered wrong by it today. Two config or import blocks that share * an opening and a closing but differ in the middle are the realistic shape. */ test("two fences that share a compact cache key are not served each other's tokens", async () => { const plugin = createCodePlugin({ themes: THEMES }); // Same length, same first 32 and same last 32 characters, different middle. const head = "const cfg = {\n alpha: 1,\n beta: 2,\n"; const tail = "\n omega: 26,\n};\nexport default cfg;\n"; const first = `${head} middle: 'AAAA',\n${tail}`; const second = `${head} middle: 'BBBB',\n${tail}`; assert.equal(first.length, second.length, "fixture must share a length"); assert.equal( first.slice(0, 32), second.slice(0, 32), "fixture must share the first 32 characters codeKey samples", ); assert.equal( first.slice(-32), second.slice(-32), "fixture must share the last 32 characters codeKey samples", ); assert.notEqual(first, second, "fixture fences must actually differ"); const rendered = async (code: string): Promise => { const result = await highlightOnce(plugin, { code, language: "ts", themes: THEMES, }); return result.tokens .map((line) => line.map((token) => token.content).join("")) .join("\n"); }; assert.match(await rendered(first), /AAAA/); const secondText = await rendered(second); assert.doesNotMatch( secondText, /AAAA/, "the second fence was served the first fence's tokens, so the reader sees code that is not in the message", ); assert.match( secondText, /BBBB/, "the second fence did not render its own contents", ); });