UNPKG

@humanspeak/svelte-markdown

Version:

Markdown and HTML renderer for Svelte 5 — built for rendering streaming AI agent output from Claude Code, ChatGPT, and agentic workflows. XSS-safe defaults, streaming-aware sanitization, token caching, TypeScript types, and Svelte 5 runes.

71 lines (70 loc) 3.21 kB
export const STREAM_BATCH_FALLBACK_MS = 16; export const STREAM_BATCH_MAX_CHARS = 256; // Largest gap an offset chunk may open before it is treated as malformed. export const STREAM_MAX_OFFSET_GAP = 1_000_000; /** * Checks whether a streaming chunk uses offset-based patching. * * @param chunk Streaming chunk passed to the imperative streaming API. * @returns True when the chunk has an `offset` field. */ export const isStreamingOffsetChunk = (chunk) => typeof chunk === 'object' && chunk !== null && 'offset' in chunk; export const getStreamingChunkInstruction = (chunk, currentMode, { currentBufferLength = 0, maxOffsetGap = STREAM_MAX_OFFSET_GAP } = {}) => { if (typeof chunk === 'string') { if (currentMode === 'offset') { return { kind: 'drop', message: 'offset mode active, string chunk dropped. Call resetStream() before switching streaming input modes.' }; } return { kind: 'append', value: chunk, nextMode: 'append' }; } if (!isStreamingOffsetChunk(chunk) || typeof chunk.value !== 'string') { return { kind: 'drop', message: 'Invalid chunk object passed to writeChunk(); expected { value: string, offset: number }.' }; } if (!Number.isSafeInteger(chunk.offset) || chunk.offset < 0) { return { kind: 'drop', message: 'Invalid offset chunk passed to writeChunk(); offset must be a non-negative safe integer.' }; } if (currentMode === 'append') { return { kind: 'drop', message: 'append mode active, offset chunk dropped. Call resetStream() before switching streaming input modes.' }; } if (chunk.offset - currentBufferLength > maxOffsetGap) { return { kind: 'drop', message: `offset chunk skipped: offset ${chunk.offset} is more than ` + `${maxOffsetGap} chars beyond the current buffer length ` + `(${currentBufferLength}).` }; } return { kind: 'offset', chunk, nextMode: 'offset' }; }; export const appendStreamingChunk = (pendingBuffer, value) => pendingBuffer + value; export const commitStreamingAppendBuffer = (sourceBuffer, pendingBuffer) => { if (pendingBuffer === '') { return { committed: false, sourceBuffer, pendingBuffer }; } return { committed: true, sourceBuffer: sourceBuffer + pendingBuffer, pendingBuffer: '' }; }; export const shouldFlushStreamingAppendBuffer = (pendingBuffer, maxChars = STREAM_BATCH_MAX_CHARS) => pendingBuffer.length >= maxChars; export const applyStreamingOffsetChunk = (source, { value, offset }, { maxOffsetGap = STREAM_MAX_OFFSET_GAP } = {}) => { const gap = Math.max(0, offset - source.length); const boundedGap = Math.min(gap, Math.max(0, maxOffsetGap)); const effectiveOffset = offset > source.length ? source.length + boundedGap : offset; const padded = boundedGap > 0 ? source + ' '.repeat(boundedGap) : source; const prefix = padded.slice(0, effectiveOffset); const suffix = padded.slice(effectiveOffset + value.length); return prefix + value + suffix; };