@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
JavaScript
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;
};