UNPKG

llm-json-fix

Version:

Fix malformed JSON outputs from Large Language Models (LLMs)

170 lines (169 loc) 5.56 kB
import { BufferLimitExceededError } from '../utils/errors'; /** * A circular buffer for efficient streaming operations */ export class StreamBuffer { /** * Create a new StreamBuffer with a specified maximum size */ constructor(capacity) { this.capacity = capacity; this.position = 0; this.size = 0; this.buffer = ' '.repeat(capacity); } /** * Reset the buffer to its initial state */ reset() { this.position = 0; this.size = 0; this.buffer = ' '.repeat(this.capacity); } /** * Get number of characters in the buffer */ getSize() { return this.size; } /** * Get the specified number of characters starting at the given index */ substr(start, length) { if (start >= this.size) { return ''; } // Adjust length if it would exceed the buffer size if (start + length > this.size) { length = this.size - start; } // Calculate position in the circular buffer // This calculation is needed for internal buffer management // eslint-disable-next-line @typescript-eslint/no-unused-vars const physicalStart = (this.position - this.size + start) % this.capacity; // Check if the substring wraps around the buffer if (physicalStart + length <= this.capacity) { // No wrap, return directly return this.buffer.substring(physicalStart, physicalStart + length); } else { // Substring wraps around the buffer const firstPart = this.buffer.substring(physicalStart, this.capacity); const secondPart = this.buffer.substring(0, (physicalStart + length) % this.capacity); return firstPart + secondPart; } } /** * Get the last `length` characters of the buffer */ getLastChars(length) { return this.substr(this.size - length, length); } /** * Get the first `length` characters of the buffer */ getFirstChars(length) { return this.substr(0, length); } /** * Get a character at the specified position */ charAt(index) { if (index < 0 || index >= this.size) { return ''; } const physicalPos = (this.position - this.size + index) % this.capacity; return this.buffer[physicalPos]; } /** * Append a chunk of text to the buffer */ append(text) { if (text.length > this.capacity) { throw new BufferLimitExceededError(`Cannot append string of length ${text.length} to buffer with capacity ${this.capacity}`, this.size); } // Remove characters that would exceed capacity const removeCount = Math.max(0, this.size + text.length - this.capacity); if (removeCount > 0) { this.size -= removeCount; } // Append each character for (let i = 0; i < text.length; i++) { this.buffer = this.buffer.substring(0, this.position) + text[i] + this.buffer.substring(this.position + 1); this.position = (this.position + 1) % this.capacity; if (this.size < this.capacity) { this.size++; } } } /** * Check if the buffer includes the given string */ includes(str) { if (str.length > this.size) { return false; } // Check if the string is present in the buffer for (let i = 0; i <= this.size - str.length; i++) { const substring = this.substr(i, str.length); if (substring === str) { return true; } } return false; } /** * Find the position of a string in the buffer */ indexOf(str, fromIndex = 0) { if (str.length > this.size) { return -1; } // Find the position of the string in the buffer for (let i = fromIndex; i <= this.size - str.length; i++) { const substring = this.substr(i, str.length); if (substring === str) { return i; } } return -1; } /** * Replace part of the buffer with a new string */ replace(start, length, replacement) { if (start < 0 || start >= this.size) { throw new Error(`Invalid start index: ${start}`); } if (length <= 0) { throw new Error(`Invalid length: ${length}`); } // Calculate position in the circular buffer // This calculation is needed for internal buffer management // eslint-disable-next-line @typescript-eslint/no-unused-vars const physicalStart = (this.position - this.size + start) % this.capacity; // Remove the specified portion and insert the replacement let newBuffer = ''; // First part before the replacement for (let i = 0; i < start; i++) { newBuffer += this.charAt(i); } // Add the replacement newBuffer += replacement; // Last part after the replacement for (let i = start + length; i < this.size; i++) { newBuffer += this.charAt(i); } // Reset and fill the buffer with the new content this.reset(); this.append(newBuffer); } /** * Get the entire content of the buffer */ toString() { return this.substr(0, this.size); } }