From d8fc724d90a48a36c36e1bdab8d4a190d433d05b Mon Sep 17 00:00:00 2001 From: agent_coder Date: Sun, 5 Jul 2026 19:35:14 +0300 Subject: [PATCH] test(ai): cover the partialOutput PRESERVE branch of the ai@6.0.134 patch (#184, review F1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch forks createOutputTransformStream: output==null skips partialOutput (the OOM fix, already tested), output!=null preserves the original cumulative accumulation. Only the skip branch was tested; the preserve branch — on which the patch's "byte-identical when an output strategy is set" safety claim rests — had no coverage, so a future re-port (patches are re-created via `pnpm patch` on every ai bump) could silently route output-set calls into the skip branch and leave partialOutput empty for object/text-output consumers, uncaught. Add a 4th test: streamText({ ..., experimental_output: Output.text() }), drain textStream, collect experimental_partialOutputStream, and assert it is non-empty and cumulative (last partial == full text "Hello, world!"). Reuses the existing makeModel() harness. Verified on the patched dist: partials are ["Hello","Hello, ","Hello, world!"]. `npx jest ai-sdk-partial-output.patch.spec.ts` → 4 passed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ai/ai-sdk-partial-output.patch.spec.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/server/src/integrations/ai/ai-sdk-partial-output.patch.spec.ts b/apps/server/src/integrations/ai/ai-sdk-partial-output.patch.spec.ts index 6121611e..d5946b04 100644 --- a/apps/server/src/integrations/ai/ai-sdk-partial-output.patch.spec.ts +++ b/apps/server/src/integrations/ai/ai-sdk-partial-output.patch.spec.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'fs'; -import { streamText } from 'ai'; +import { streamText, Output } from 'ai'; import { MockLanguageModelV3, simulateReadableStream } from 'ai/test'; /** @@ -80,6 +80,38 @@ describe('ai@6.0.134 pnpm patch: no partialOutput accumulation without an output expect(partials).toEqual([]); }); + it('preserves cumulative partialOutput when the caller DOES request an output strategy', async () => { + // PRESERVE-BRANCH GUARD: the patch only short-circuits partialOutput when + // `output == null`. When an output strategy IS set (here Output.text()), + // createOutputTransformStream must fall through to the ORIGINAL code path + // and keep publishing cumulative snapshots, so object/text-output consumers + // behave byte-identically to unpatched ai. A careless re-port that routed + // output-set calls into the skip branch would leave partialOutput empty and + // silently break those consumers — this test is the tripwire for that. + const result = streamText({ + model: makeModel(), + prompt: 'hi', + experimental_output: Output.text(), + }); + + // Drain the primary stream fully and accumulate the complete output text. + let fullText = ''; + for await (const delta of result.textStream) { + fullText += delta; + } + + const partials: string[] = []; + for await (const partial of result.experimental_partialOutputStream) { + partials.push(partial); + } + + // With a strategy set, partialOutput must be PRESERVED (non-empty) and + // cumulative: the last emitted partial equals the full accumulated text. + expect(partials.length).toBeGreaterThan(0); + expect(partials[partials.length - 1]).toBe(fullText); + expect(fullText).toBe('Hello, world!'); + }); + it('both installed dist builds (CJS and ESM) carry the patch marker', () => { // Secondary guard: pins the patch to BOTH bundles the SDK ships, since // the NestJS server consumes CJS while other tooling may load ESM.