diff --git a/src/channels/streaming-flat-key-deprecation.test.ts b/src/channels/streaming-flat-key-deprecation.test.ts index bc237fcc4f3e..2a613ac2593e 100644 --- a/src/channels/streaming-flat-key-deprecation.test.ts +++ b/src/channels/streaming-flat-key-deprecation.test.ts @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { resetFlatStreamingKeyDeprecationWarningsForTest } from "./streaming-flat-key-deprecation.js"; import { + resolveChannelPreviewStreamMode, resolveChannelStreamingBlockCoalesce, resolveChannelStreamingBlockEnabled, resolveChannelStreamingChunkMode, @@ -56,4 +57,15 @@ describe("flat streaming key deprecation warning", () => { expect(resolveChannelStreamingBlockCoalesce({})).toBeUndefined(); expect(loggerMocks.warn).not.toHaveBeenCalled(); }); + + it.each([ + { scalar: "block", expected: "block" }, + { scalar: true, expected: "partial" }, + ])("warns once when the scalar streaming fallback is used", ({ scalar, expected }) => { + expect(resolveChannelPreviewStreamMode({ streaming: scalar }, "off")).toBe(expected); + expect(resolveChannelPreviewStreamMode({ streaming: scalar }, "off")).toBe(expected); + expect(loggerMocks.warn).toHaveBeenCalledTimes(1); + expect(loggerMocks.warn.mock.calls[0]?.[0]).toContain('"streaming"'); + expect(loggerMocks.warn.mock.calls[0]?.[0]).toContain("streaming.mode"); + }); }); diff --git a/src/channels/streaming.test.ts b/src/channels/streaming.test.ts index 9eeb57541c91..bef1379f1fa6 100644 --- a/src/channels/streaming.test.ts +++ b/src/channels/streaming.test.ts @@ -95,9 +95,8 @@ describe("buildChannelProgressDraftLine", () => { }); describe("streaming config resolution", () => { - // Flat delivery keys stay canonical for channels without a nested streaming - // schema and for SDK plugins; mode-family aliases (streamMode, scalar - // streaming, nativeStreaming) are doctor-migrated and unread at runtime. + // Flat delivery keys remain external SDK compatibility fallbacks. Bundled + // schemas are nested-only; mode-family aliases stay doctor-only. it("resolves flat delivery keys while ignoring mode-family aliases", () => { const legacyEntry = { streamMode: "block", @@ -135,8 +134,8 @@ describe("streaming config resolution", () => { expect(resolveChannelStreamingNativeTransport(entry)).toBe(false); }); - it("keeps scalar streaming support for channels whose schema allows it", () => { - // Mattermost's schema accepts a scalar mode string or boolean as canonical. + it("keeps the scalar streaming fallback for external SDK plugin configs", () => { + // Bundled schemas are nested-only; this compatibility path is deprecated. expect(resolveChannelPreviewStreamMode({ streaming: "block" }, "partial")).toBe("block"); expect(resolveChannelPreviewStreamMode({ streaming: true }, "off")).toBe("partial"); expect(resolveChannelPreviewStreamMode({ streaming: false }, "partial")).toBe("off"); diff --git a/src/channels/streaming.ts b/src/channels/streaming.ts index 9a7bb0c6cca7..65e73dadcce6 100644 --- a/src/channels/streaming.ts +++ b/src/channels/streaming.ts @@ -41,13 +41,10 @@ export type StreamingCompatEntry = { draftChunk?: unknown; }; -// Nested streaming config wins. Every bundled channel now uses a nested-only -// streaming schema with doctor migrating the flat spellings, so in-tree the -// flat delivery keys (chunkMode, blockStreaming, blockStreamingCoalesce, -// draftChunk) are legacy config. The fallback reads below serve external SDK -// plugin configs only and emit a once-per-key deprecation warning; remove -// them (and the flat StreamingCompatEntry fields) when the next release train -// closes the SDK deprecation window. +// Bundled schemas are nested-only; doctor migrates flat delivery keys +// (chunkMode, blockStreaming, blockStreamingCoalesce, draftChunk) and scalar +// `streaming`. External SDK fallbacks warn once; remove them, the flat +// StreamingCompatEntry fields, and scalar support after the next release train. // Mode-family aliases (streamMode) are doctor-only and stay unread here. function asObjectRecord(value: unknown): Record | null { @@ -922,13 +919,16 @@ export function resolveChannelPreviewStreamMode( // Scalar `streaming` (mode string or boolean) is rejected by every bundled // channel schema and doctor-migrated to streaming.mode; the read here stays // only for external SDK plugin configs that predate the nested shape. - const parsedStreaming = parsePreviewStreamingMode( - getChannelStreamingConfigObject(entry)?.mode ?? entry?.streaming, - ); + const streamingConfig = getChannelStreamingConfigObject(entry); + const parsedStreaming = parsePreviewStreamingMode(streamingConfig?.mode ?? entry?.streaming); if (parsedStreaming) { + if (!streamingConfig) { + warnFlatStreamingKeyFallback("streaming", "mode"); + } return parsedStreaming; } if (typeof entry?.streaming === "boolean") { + warnFlatStreamingKeyFallback("streaming", "mode"); return entry.streaming ? "partial" : "off"; } return defaultMode; diff --git a/src/plugin-sdk/channel-streaming.test.ts b/src/plugin-sdk/channel-streaming.test.ts index 2f8a504210bb..bf80ba4e61a2 100644 --- a/src/plugin-sdk/channel-streaming.test.ts +++ b/src/plugin-sdk/channel-streaming.test.ts @@ -121,10 +121,9 @@ describe("channel-streaming", () => { }); it("resolves flat delivery keys when no nested streaming config exists", () => { - // Flat delivery keys stay canonical for channels without a nested - // streaming schema (Mattermost, WhatsApp, Google Chat, IRC, Signal) and - // for external SDK plugins; mode-family aliases (streamMode, scalar - // streaming, nativeStreaming) are doctor-only and stay unread. + // Bundled channel schemas are nested-only. Flat delivery keys remain + // compatibility fallbacks for external SDK plugins; mode-family aliases + // are doctor-only and stay unread. const entry = { chunkMode: "newline", blockStreaming: true,