|
1 | 1 | import { ReadableStream, type Response } from './_shims/index'; |
2 | 2 | import { OpenAIError } from './error'; |
| 3 | +import { LineDecoder } from './internal/decoders/line'; |
3 | 4 |
|
4 | 5 | import { APIError } from 'openai/error'; |
5 | 6 |
|
@@ -343,117 +344,6 @@ class SSEDecoder { |
343 | 344 | } |
344 | 345 | } |
345 | 346 |
|
346 | | -/** |
347 | | - * A re-implementation of httpx's `LineDecoder` in Python that handles incrementally |
348 | | - * reading lines from text. |
349 | | - * |
350 | | - * https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258 |
351 | | - */ |
352 | | -class LineDecoder { |
353 | | - // prettier-ignore |
354 | | - static NEWLINE_CHARS = new Set(['\n', '\r']); |
355 | | - static NEWLINE_REGEXP = /\r\n|[\n\r]/g; |
356 | | - |
357 | | - buffer: string[]; |
358 | | - trailingCR: boolean; |
359 | | - textDecoder: any; // TextDecoder found in browsers; not typed to avoid pulling in either "dom" or "node" types. |
360 | | - |
361 | | - constructor() { |
362 | | - this.buffer = []; |
363 | | - this.trailingCR = false; |
364 | | - } |
365 | | - |
366 | | - decode(chunk: Bytes): string[] { |
367 | | - let text = this.decodeText(chunk); |
368 | | - |
369 | | - if (this.trailingCR) { |
370 | | - text = '\r' + text; |
371 | | - this.trailingCR = false; |
372 | | - } |
373 | | - if (text.endsWith('\r')) { |
374 | | - this.trailingCR = true; |
375 | | - text = text.slice(0, -1); |
376 | | - } |
377 | | - |
378 | | - if (!text) { |
379 | | - return []; |
380 | | - } |
381 | | - |
382 | | - const trailingNewline = LineDecoder.NEWLINE_CHARS.has(text[text.length - 1] || ''); |
383 | | - let lines = text.split(LineDecoder.NEWLINE_REGEXP); |
384 | | - |
385 | | - // if there is a trailing new line then the last entry will be an empty |
386 | | - // string which we don't care about |
387 | | - if (trailingNewline) { |
388 | | - lines.pop(); |
389 | | - } |
390 | | - |
391 | | - if (lines.length === 1 && !trailingNewline) { |
392 | | - this.buffer.push(lines[0]!); |
393 | | - return []; |
394 | | - } |
395 | | - |
396 | | - if (this.buffer.length > 0) { |
397 | | - lines = [this.buffer.join('') + lines[0], ...lines.slice(1)]; |
398 | | - this.buffer = []; |
399 | | - } |
400 | | - |
401 | | - if (!trailingNewline) { |
402 | | - this.buffer = [lines.pop() || '']; |
403 | | - } |
404 | | - |
405 | | - return lines; |
406 | | - } |
407 | | - |
408 | | - decodeText(bytes: Bytes): string { |
409 | | - if (bytes == null) return ''; |
410 | | - if (typeof bytes === 'string') return bytes; |
411 | | - |
412 | | - // Node: |
413 | | - if (typeof Buffer !== 'undefined') { |
414 | | - if (bytes instanceof Buffer) { |
415 | | - return bytes.toString(); |
416 | | - } |
417 | | - if (bytes instanceof Uint8Array) { |
418 | | - return Buffer.from(bytes).toString(); |
419 | | - } |
420 | | - |
421 | | - throw new OpenAIError( |
422 | | - `Unexpected: received non-Uint8Array (${bytes.constructor.name}) stream chunk in an environment with a global "Buffer" defined, which this library assumes to be Node. Please report this error.`, |
423 | | - ); |
424 | | - } |
425 | | - |
426 | | - // Browser |
427 | | - if (typeof TextDecoder !== 'undefined') { |
428 | | - if (bytes instanceof Uint8Array || bytes instanceof ArrayBuffer) { |
429 | | - this.textDecoder ??= new TextDecoder('utf8'); |
430 | | - return this.textDecoder.decode(bytes); |
431 | | - } |
432 | | - |
433 | | - throw new OpenAIError( |
434 | | - `Unexpected: received non-Uint8Array/ArrayBuffer (${ |
435 | | - (bytes as any).constructor.name |
436 | | - }) in a web platform. Please report this error.`, |
437 | | - ); |
438 | | - } |
439 | | - |
440 | | - throw new OpenAIError( |
441 | | - `Unexpected: neither Buffer nor TextDecoder are available as globals. Please report this error.`, |
442 | | - ); |
443 | | - } |
444 | | - |
445 | | - flush(): string[] { |
446 | | - if (!this.buffer.length && !this.trailingCR) { |
447 | | - return []; |
448 | | - } |
449 | | - |
450 | | - const lines = [this.buffer.join('')]; |
451 | | - this.buffer = []; |
452 | | - this.trailingCR = false; |
453 | | - return lines; |
454 | | - } |
455 | | -} |
456 | | - |
457 | 347 | /** This is an internal helper function that's just used for testing */ |
458 | 348 | export function _decodeChunks(chunks: string[]): string[] { |
459 | 349 | const decoder = new LineDecoder(); |
|
0 commit comments