@xsai/stream-text 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,23 @@
1
- import { ChatOptions, FinishReason } from '@xsai/shared-chat';
1
+ import { FinishReason, Usage, ChatOptions } from '@xsai/shared-chat';
2
2
 
3
+ interface ChunkResult {
4
+ choices: {
5
+ delta: {
6
+ content: string;
7
+ role: 'assistant';
8
+ };
9
+ finish_reason?: FinishReason;
10
+ index: number;
11
+ }[];
12
+ created: number;
13
+ id: string;
14
+ model: string;
15
+ object: 'chat.completion.chunk';
16
+ system_fingerprint: string;
17
+ usage?: Usage;
18
+ }
3
19
  interface StreamTextOptions extends ChatOptions {
20
+ onChunk?: (chunk: ChunkResult) => Promise<void> | void;
4
21
  /** if you want to disable stream, use `@xsai/generate-{text,object}` */
5
22
  stream?: never;
6
23
  streamOptions?: {
@@ -12,36 +29,15 @@ interface StreamTextOptions extends ChatOptions {
12
29
  usage?: boolean;
13
30
  };
14
31
  }
15
- interface StreamTextResponseUsage {
16
- completion_tokens: number;
17
- prompt_tokens: number;
18
- total_tokens: number;
19
- }
20
32
  interface StreamTextResult {
21
- chunkStream: ReadableStream<StreamTextResponse>;
33
+ chunkStream: ReadableStream<ChunkResult>;
22
34
  finishReason?: FinishReason;
23
35
  textStream: ReadableStream<string>;
24
- usage?: StreamTextResponseUsage;
25
- }
26
- interface StreamTextResponse {
27
- choices: {
28
- delta: {
29
- content: string;
30
- role: 'assistant';
31
- };
32
- finish_reason?: FinishReason;
33
- index: number;
34
- }[];
35
- created: number;
36
- id: string;
37
- model: string;
38
- object: 'chat.completion.chunk';
39
- system_fingerprint: string;
40
- usage?: StreamTextResponseUsage;
36
+ usage?: Usage;
41
37
  }
42
38
  /**
43
39
  * @experimental WIP, does not support function calling (tools).
44
40
  */
45
41
  declare const streamText: (options: StreamTextOptions) => Promise<StreamTextResult>;
46
42
 
47
- export { type StreamTextOptions, type StreamTextResponse, type StreamTextResponseUsage, type StreamTextResult, streamText as default, streamText };
43
+ export { type ChunkResult, type StreamTextOptions, type StreamTextResult, streamText as default, streamText };
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { chat } from '@xsai/shared-chat';
2
2
 
3
- const dataHeaderPrefix = "data: ";
4
- const dataErrorPrefix = `{"error":`;
3
+ const chunkHeaderPrefix = "data:";
5
4
  const streamText = async (options) => await chat({
6
5
  ...options,
7
6
  stream: true
@@ -9,33 +8,40 @@ const streamText = async (options) => await chat({
9
8
  const decoder = new TextDecoder();
10
9
  let finishReason;
11
10
  let usage;
11
+ const processLine = async (line, controller) => {
12
+ if (!line || !line.startsWith(chunkHeaderPrefix))
13
+ return;
14
+ const content = line.slice(chunkHeaderPrefix.length);
15
+ const data = content.startsWith(" ") ? content.slice(1) : content;
16
+ if (data === "[DONE]") {
17
+ controller.terminate();
18
+ return true;
19
+ }
20
+ if (data.startsWith("{") && data.includes('"error":')) {
21
+ controller.error(new Error(`Error from server: ${data}`));
22
+ return true;
23
+ }
24
+ const chunk = JSON.parse(data);
25
+ controller.enqueue(chunk);
26
+ if (options.onChunk)
27
+ await options.onChunk(chunk);
28
+ if (chunk.choices[0].finish_reason) {
29
+ finishReason = chunk.choices[0].finish_reason;
30
+ }
31
+ if (chunk.usage) {
32
+ usage = chunk.usage;
33
+ }
34
+ };
12
35
  let buffer = "";
13
36
  const rawChunkStream = res.body.pipeThrough(new TransformStream({
14
- transform: (chunk, controller) => {
15
- buffer += decoder.decode(chunk);
16
- const lines = buffer.split("\n\n");
37
+ transform: async (chunk, controller) => {
38
+ const text = decoder.decode(chunk, { stream: true });
39
+ buffer += text;
40
+ const lines = buffer.split("\n");
17
41
  buffer = lines.pop() || "";
18
42
  for (const line of lines) {
19
- if (!line || !line.startsWith(dataHeaderPrefix)) {
20
- continue;
21
- }
22
- if (line.startsWith(dataErrorPrefix)) {
23
- controller.error(new Error(`Error from server: ${line}`));
24
- break;
25
- }
26
- const lineWithoutPrefix = line.slice(dataHeaderPrefix.length);
27
- if (lineWithoutPrefix === "[DONE]") {
28
- controller.terminate();
43
+ if (await processLine(line, controller))
29
44
  break;
30
- }
31
- const data = JSON.parse(lineWithoutPrefix);
32
- controller.enqueue(data);
33
- if (data.choices[0].finish_reason) {
34
- finishReason = data.choices[0].finish_reason;
35
- }
36
- if (data.usage) {
37
- usage = data.usage;
38
- }
39
45
  }
40
46
  }
41
47
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsai/stream-text",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "author": "Moeru AI",
6
6
  "license": "MIT",