@anthropic-ai/bedrock-sdk
Version:
The official TypeScript library for the Anthropic Bedrock API
136 lines (114 loc) • 3.89 kB
text/typescript
import { concatBytes, decodeUTF8, encodeUTF8 } from '../utils/bytes';
export type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
* reading lines from text.
*
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
*/
export class LineDecoder {
// prettier-ignore
static NEWLINE_CHARS = new Set(['\n', '\r']);
static NEWLINE_REGEXP = /\r\n|[\n\r]/g;
#buffer: Uint8Array;
#carriageReturnIndex: number | null;
constructor() {
this.#buffer = new Uint8Array();
this.#carriageReturnIndex = null;
}
decode(chunk: Bytes): string[] {
if (chunk == null) {
return [];
}
const binaryChunk =
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
this.#buffer = concatBytes([this.#buffer, binaryChunk]);
const lines: string[] = [];
let patternIndex;
while ((patternIndex = findNewlineIndex(this.#buffer, this.#carriageReturnIndex)) != null) {
if (patternIndex.carriage && this.#carriageReturnIndex == null) {
// skip until we either get a corresponding `\n`, a new `\r` or nothing
this.#carriageReturnIndex = patternIndex.index;
continue;
}
// we got double \r or \rtext\n
if (
this.#carriageReturnIndex != null &&
(patternIndex.index !== this.#carriageReturnIndex + 1 || patternIndex.carriage)
) {
lines.push(decodeUTF8(this.#buffer.subarray(0, this.#carriageReturnIndex - 1)));
this.#buffer = this.#buffer.subarray(this.#carriageReturnIndex);
this.#carriageReturnIndex = null;
continue;
}
const endIndex =
this.#carriageReturnIndex !== null ? patternIndex.preceding - 1 : patternIndex.preceding;
const line = decodeUTF8(this.#buffer.subarray(0, endIndex));
lines.push(line);
this.#buffer = this.#buffer.subarray(patternIndex.index);
this.#carriageReturnIndex = null;
}
return lines;
}
flush(): string[] {
if (!this.#buffer.length) {
return [];
}
return this.decode('\n');
}
}
/**
* This function searches the buffer for the end patterns, (\r or \n)
* and returns an object with the index preceding the matched newline and the
* index after the newline char. `null` is returned if no new line is found.
*
* ```ts
* findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 }
* ```
*/
function findNewlineIndex(
buffer: Uint8Array,
startIndex: number | null,
): { preceding: number; index: number; carriage: boolean } | null {
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = startIndex ?? 0; i < buffer.length; i++) {
if (buffer[i] === newline) {
return { preceding: i, index: i + 1, carriage: false };
}
if (buffer[i] === carriage) {
return { preceding: i, index: i + 1, carriage: true };
}
}
return null;
}
export function findDoubleNewlineIndex(buffer: Uint8Array): number {
// This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n)
// and returns the index right after the first occurrence of any pattern,
// or -1 if none of the patterns are found.
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = 0; i < buffer.length - 1; i++) {
if (buffer[i] === newline && buffer[i + 1] === newline) {
// \n\n
return i + 2;
}
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
// \r\r
return i + 2;
}
if (
buffer[i] === carriage &&
buffer[i + 1] === newline &&
i + 3 < buffer.length &&
buffer[i + 2] === carriage &&
buffer[i + 3] === newline
) {
// \r\n\r\n
return i + 4;
}
}
return -1;
}