Spaces:
Paused
Paused
| import type { OpenClawConfig } from "../../config/config.js"; | |
| import type { BlockStreamingCoalesceConfig } from "../../config/types.js"; | |
| import { getChannelDock } from "../../channels/dock.js"; | |
| import { normalizeChannelId } from "../../channels/plugins/index.js"; | |
| import { normalizeAccountId } from "../../routing/session-key.js"; | |
| import { | |
| INTERNAL_MESSAGE_CHANNEL, | |
| listDeliverableMessageChannels, | |
| } from "../../utils/message-channel.js"; | |
| import { resolveTextChunkLimit, type TextChunkProvider } from "../chunk.js"; | |
| const DEFAULT_BLOCK_STREAM_MIN = 800; | |
| const DEFAULT_BLOCK_STREAM_MAX = 1200; | |
| const DEFAULT_BLOCK_STREAM_COALESCE_IDLE_MS = 1000; | |
| const getBlockChunkProviders = () => | |
| new Set<TextChunkProvider>([...listDeliverableMessageChannels(), INTERNAL_MESSAGE_CHANNEL]); | |
| function normalizeChunkProvider(provider?: string): TextChunkProvider | undefined { | |
| if (!provider) { | |
| return undefined; | |
| } | |
| const cleaned = provider.trim().toLowerCase(); | |
| return getBlockChunkProviders().has(cleaned as TextChunkProvider) | |
| ? (cleaned as TextChunkProvider) | |
| : undefined; | |
| } | |
| type ProviderBlockStreamingConfig = { | |
| blockStreamingCoalesce?: BlockStreamingCoalesceConfig; | |
| accounts?: Record<string, { blockStreamingCoalesce?: BlockStreamingCoalesceConfig }>; | |
| }; | |
| function resolveProviderBlockStreamingCoalesce(params: { | |
| cfg: OpenClawConfig | undefined; | |
| providerKey?: TextChunkProvider; | |
| accountId?: string | null; | |
| }): BlockStreamingCoalesceConfig | undefined { | |
| const { cfg, providerKey, accountId } = params; | |
| if (!cfg || !providerKey) { | |
| return undefined; | |
| } | |
| const providerCfg = (cfg as Record<string, unknown>)[providerKey]; | |
| if (!providerCfg || typeof providerCfg !== "object") { | |
| return undefined; | |
| } | |
| const normalizedAccountId = normalizeAccountId(accountId); | |
| const typed = providerCfg as ProviderBlockStreamingConfig; | |
| const accountCfg = typed.accounts?.[normalizedAccountId]; | |
| return accountCfg?.blockStreamingCoalesce ?? typed.blockStreamingCoalesce; | |
| } | |
| export type BlockStreamingCoalescing = { | |
| minChars: number; | |
| maxChars: number; | |
| idleMs: number; | |
| joiner: string; | |
| }; | |
| export function resolveBlockStreamingChunking( | |
| cfg: OpenClawConfig | undefined, | |
| provider?: string, | |
| accountId?: string | null, | |
| ): { | |
| minChars: number; | |
| maxChars: number; | |
| breakPreference: "paragraph" | "newline" | "sentence"; | |
| } { | |
| const providerKey = normalizeChunkProvider(provider); | |
| const providerId = providerKey ? normalizeChannelId(providerKey) : null; | |
| const providerChunkLimit = providerId | |
| ? getChannelDock(providerId)?.outbound?.textChunkLimit | |
| : undefined; | |
| const textLimit = resolveTextChunkLimit(cfg, providerKey, accountId, { | |
| fallbackLimit: providerChunkLimit, | |
| }); | |
| const chunkCfg = cfg?.agents?.defaults?.blockStreamingChunk; | |
| // Note: chunkMode="newline" used to imply splitting on each newline, but outbound | |
| // delivery now treats it as paragraph-aware chunking (only split on blank lines). | |
| // Block streaming should follow the same rule, so we do NOT special-case newline | |
| // mode here. | |
| // (chunkMode no longer alters block streaming behavior) | |
| const maxRequested = Math.max(1, Math.floor(chunkCfg?.maxChars ?? DEFAULT_BLOCK_STREAM_MAX)); | |
| const maxChars = Math.max(1, Math.min(maxRequested, textLimit)); | |
| const minFallback = DEFAULT_BLOCK_STREAM_MIN; | |
| const minRequested = Math.max(1, Math.floor(chunkCfg?.minChars ?? minFallback)); | |
| const minChars = Math.min(minRequested, maxChars); | |
| const breakPreference = | |
| chunkCfg?.breakPreference === "newline" || chunkCfg?.breakPreference === "sentence" | |
| ? chunkCfg.breakPreference | |
| : "paragraph"; | |
| return { minChars, maxChars, breakPreference }; | |
| } | |
| export function resolveBlockStreamingCoalescing( | |
| cfg: OpenClawConfig | undefined, | |
| provider?: string, | |
| accountId?: string | null, | |
| chunking?: { | |
| minChars: number; | |
| maxChars: number; | |
| breakPreference: "paragraph" | "newline" | "sentence"; | |
| }, | |
| ): BlockStreamingCoalescing | undefined { | |
| const providerKey = normalizeChunkProvider(provider); | |
| // Note: chunkMode="newline" is paragraph-aware in outbound delivery (blank-line splits), | |
| // so block streaming should not disable coalescing or flush per single newline. | |
| const providerId = providerKey ? normalizeChannelId(providerKey) : null; | |
| const providerChunkLimit = providerId | |
| ? getChannelDock(providerId)?.outbound?.textChunkLimit | |
| : undefined; | |
| const textLimit = resolveTextChunkLimit(cfg, providerKey, accountId, { | |
| fallbackLimit: providerChunkLimit, | |
| }); | |
| const providerDefaults = providerId | |
| ? getChannelDock(providerId)?.streaming?.blockStreamingCoalesceDefaults | |
| : undefined; | |
| const providerCfg = resolveProviderBlockStreamingCoalesce({ | |
| cfg, | |
| providerKey, | |
| accountId, | |
| }); | |
| const coalesceCfg = providerCfg ?? cfg?.agents?.defaults?.blockStreamingCoalesce; | |
| const minRequested = Math.max( | |
| 1, | |
| Math.floor( | |
| coalesceCfg?.minChars ?? | |
| providerDefaults?.minChars ?? | |
| chunking?.minChars ?? | |
| DEFAULT_BLOCK_STREAM_MIN, | |
| ), | |
| ); | |
| const maxRequested = Math.max(1, Math.floor(coalesceCfg?.maxChars ?? textLimit)); | |
| const maxChars = Math.max(1, Math.min(maxRequested, textLimit)); | |
| const minChars = Math.min(minRequested, maxChars); | |
| const idleMs = Math.max( | |
| 0, | |
| Math.floor( | |
| coalesceCfg?.idleMs ?? providerDefaults?.idleMs ?? DEFAULT_BLOCK_STREAM_COALESCE_IDLE_MS, | |
| ), | |
| ); | |
| const preference = chunking?.breakPreference ?? "paragraph"; | |
| const joiner = preference === "sentence" ? " " : preference === "newline" ? "\n" : "\n\n"; | |
| return { | |
| minChars, | |
| maxChars, | |
| idleMs, | |
| joiner, | |
| }; | |
| } | |