import test from "node:test"; import assert from "node:assert/strict"; import { newStreamCtx, processFrame, isComposerModel, visibleComposerContentFromThinking, composerReasoningRemainder, type StreamCtx, } from "../../open-sse/executors/cursor"; // ─── Wire-format helpers (mirror cursor-streaming.test.ts) ──────────────────── function v(n: number): Buffer { const out: number[] = []; while (n > 0x7f) { out.push((n & 0x7f) | 0x80); n >>>= 7; } out.push(n); return Buffer.from(out); } function tag(field: number, wireType: number): Buffer { return v((field << 3) | wireType); } function lenPrefixed(field: number, payload: Buffer): Buffer { return Buffer.concat([tag(field, 2), v(payload.length), payload]); } // AgentServerMessage { interaction_update (1): { thinking_delta (4): { text (1): str } } } function buildThinkingDeltaPayload(text: string): Buffer { const tdu = lenPrefixed(1, Buffer.from(text, "utf8")); const iu = lenPrefixed(4, tdu); return lenPrefixed(1, iu); } function parseSSE(text: string): Array> { return text .split("\n\n") .filter((c) => c.startsWith("data: ")) .map((c) => c.slice("data: ".length)) .filter((d) => d !== "[DONE]") .map((d) => JSON.parse(d)); } // ─── Pure helpers ──────────────────────────────────────────────────────────── test("isComposerModel matches composer + composer-* (case-insensitive, vendor prefix tolerated)", () => { assert.equal(isComposerModel("composer"), true); assert.equal(isComposerModel("composer-2.5"), true); assert.equal(isComposerModel("composer-2.5-fast"), true); assert.equal(isComposerModel("cu/composer-2.5"), true); assert.equal(isComposerModel("CURSOR/Composer-2.5"), true); assert.equal(isComposerModel("gpt-5.3-codex"), false); assert.equal(isComposerModel("claude-4-sonnet"), false); assert.equal(isComposerModel("composer2"), false); assert.equal(isComposerModel(""), false); }); test("visibleComposerContentFromThinking returns suffix after last (trim-start)", () => { assert.equal( visibleComposerContentFromThinking("private reasoningOK"), "OK" ); assert.equal( visibleComposerContentFromThinking("ab final"), "final" ); assert.equal(visibleComposerContentFromThinking("no marker yet"), ""); assert.equal(visibleComposerContentFromThinking(""), ""); assert.equal(visibleComposerContentFromThinking("ends with"), ""); }); test("visibleComposerContentFromThinking strips `<|final|>` sentinel markers (full-width + ASCII)", () => { // Full-width pipe sentinels (decolua/9router#1316). assert.equal( visibleComposerContentFromThinking("reasoning<|final|>OK_PR1316<|/final|>"), "OK_PR1316" ); // ASCII pipe sentinels. assert.equal( visibleComposerContentFromThinking("reasoning<|final|>HELLO<|/final|>"), "HELLO" ); // Open marker without a closing tag still gets stripped. assert.equal( visibleComposerContentFromThinking("r<|final|>just open"), "just open" ); // No sentinel — plain suffix is unchanged. assert.equal( visibleComposerContentFromThinking("reasoningplain answer"), "plain answer" ); }); test("visibleComposerContentFromThinking holds back a partial opening marker until complete", () => { // A streamed chunk delivered only the start of the sentinel — emit nothing yet. assert.equal(visibleComposerContentFromThinking("r<"), ""); assert.equal(visibleComposerContentFromThinking("r<|fin"), ""); assert.equal(visibleComposerContentFromThinking("r<|fin"), ""); // Once the full marker + payload arrive the real content surfaces. assert.equal( visibleComposerContentFromThinking("r<|final|>NOW_VISIBLE"), "NOW_VISIBLE" ); }); test("composerReasoningRemainder returns only the hidden portion before last ", () => { assert.equal( composerReasoningRemainder("private reasoningOK"), "private reasoning" ); assert.equal( composerReasoningRemainder("just hidden, no marker"), "just hidden, no marker" ); assert.equal(composerReasoningRemainder(""), ""); }); // ─── Composer thinking handling via processFrame ───────────────────────────── test("Composer streaming: emits visible suffix after as content deltas; hidden never leaks as content", () => { const chunks: string[] = []; const ctx: StreamCtx = newStreamCtx("composer-2.5-fast", (c) => chunks.push(c)); processFrame(buildThinkingDeltaPayload("private reasoning"), ctx, new Set()); processFrame( buildThinkingDeltaPayload(" that must not leakO"), ctx, new Set() ); processFrame(buildThinkingDeltaPayload("K"), ctx, new Set()); const sseText = chunks.join(""); const events = parseSSE(sseText); const content = events .map((e) => { const choices = (e as { choices?: Array<{ delta?: { content?: string } }> }).choices; return choices?.[0]?.delta?.content ?? ""; }) .join(""); assert.equal(content, "OK"); // Aggregated ctx.totalText must mirror the visible content so the // non-streaming aggregator surfaces it as message.content unchanged. assert.equal(ctx.totalText, "OK"); // Composer must NOT emit reasoning_content for the visible suffix portion // — the hidden reasoning may still appear as reasoning_content deltas, but // the literal post- text must never appear as reasoning_content. const reasoningStream = events .map((e) => { const choices = (e as { choices?: Array<{ delta?: { reasoning_content?: string } }> }) .choices; return choices?.[0]?.delta?.reasoning_content ?? ""; }) .join(""); assert.ok( !reasoningStream.includes("OK"), "visible suffix must not be duplicated into reasoning_content" ); }); test("Composer non-streaming aggregation: thinking with populates totalText with visible suffix", () => { const chunks: string[] = []; const ctx: StreamCtx = newStreamCtx("cu/composer-2.5", (c) => chunks.push(c)); processFrame( buildThinkingDeltaPayload("private reasoning that must not leakOK"), ctx, new Set() ); assert.equal(ctx.totalText, "OK"); }); test("Composer streaming: partial `<|final|>` sentinel split across chunks never leaks", () => { const chunks: string[] = []; const ctx: StreamCtx = newStreamCtx("cu/composer-2.5", (c) => chunks.push(c)); // The sentinel arrives byte-fragmented across three thinking-delta frames. processFrame(buildThinkingDeltaPayload("reasoning<|fina"), ctx, new Set()); processFrame(buildThinkingDeltaPayload("l|>OK_S"), ctx, new Set()); processFrame(buildThinkingDeltaPayload("TREAM"), ctx, new Set()); const events = parseSSE(chunks.join("")); const content = events .map((e) => { const choices = (e as { choices?: Array<{ delta?: { content?: string } }> }).choices; return choices?.[0]?.delta?.content ?? ""; }) .join(""); assert.equal(content, "OK_STREAM"); assert.equal(ctx.totalText, "OK_STREAM"); assert.ok(!chunks.join("").includes("final"), "sentinel literal must not leak"); assert.ok(!chunks.join("").includes("|"), "full-width pipe must not leak"); }); test("Non-Composer model: thinking field stays in reasoning_content (unchanged contract)", () => { const chunks: string[] = []; const ctx: StreamCtx = newStreamCtx("gpt-5.3-codex", (c) => chunks.push(c)); processFrame( buildThinkingDeltaPayload("hiddenSHOULD_NOT_APPEAR"), ctx, new Set() ); const sseText = chunks.join(""); assert.ok(sseText.includes("reasoning_content"), "reasoning_content delta missing"); const events = parseSSE(sseText); const content = events .map((e) => { const choices = (e as { choices?: Array<{ delta?: { content?: string } }> }).choices; return choices?.[0]?.delta?.content ?? ""; }) .join(""); assert.equal(content, "", "non-Composer must not surface thinking as content"); assert.equal(ctx.totalText, "", "non-Composer must not populate totalText from thinking"); });