koishi-plugin-chatluna 1.0.0-beta.39 → 1.0.0-beta.40

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.
@@ -1,3 +1,21 @@
1
1
  import * as fetchType from 'undici/types/fetch';
2
+ /**
3
+ * Event type push by {@link createParser}
4
+ */
5
+ export type SSEEvent = {
6
+ /**
7
+ * event field (name)
8
+ */
9
+ event?: string;
10
+ /**
11
+ * data field
12
+ */
13
+ data?: string;
14
+ /**
15
+ * comments in event
16
+ */
17
+ comments?: string[];
18
+ } & Record<string, string>;
19
+ export declare function createParser(): (data: string) => Generator<SSEEvent, void, unknown>;
2
20
  export declare function sse(response: fetchType.Response | ReadableStreamDefaultReader<string>, onEvent?: (rawData: string) => Promise<string | boolean | void>, cacheCount?: number): Promise<void>;
3
- export declare function sseIterable(response: fetchType.Response | ReadableStreamDefaultReader<string>, checkedFunction?: (data: string, event?: string, kvMap?: Record<string, string>) => boolean, mappedFunction?: (data: string) => string | Error, cacheCount?: number): AsyncGenerator<string, string, unknown>;
21
+ export declare function sseIterable(response: fetchType.Response | ReadableStreamDefaultReader<string>): AsyncGenerator<string, string, unknown>;
package/lib/utils/sse.js CHANGED
@@ -1,7 +1,120 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sseIterable = exports.sse = void 0;
3
+ exports.sseIterable = exports.sse = exports.createParser = void 0;
4
4
  const error_1 = require("./error");
5
+ const BOM = 0xfeff;
6
+ const LF = 0x000a;
7
+ const CR = 0x000d;
8
+ const SPACE = 0x0020;
9
+ const COLON = 0x003a;
10
+ function createParser() {
11
+ let state = 'stream';
12
+ let temp = {};
13
+ let comment = '';
14
+ let fieldName = '';
15
+ let fieldValue = '';
16
+ // eslint-disable-next-line generator-star-spacing
17
+ function* parse(data) {
18
+ const cursor = data[Symbol.iterator]();
19
+ let value = { done: false, value: '' };
20
+ const looks = [];
21
+ function lookNext(ignoreIfFn) {
22
+ next();
23
+ if (value.value === undefined)
24
+ return;
25
+ if (!ignoreIfFn(value)) {
26
+ looks.push(value);
27
+ }
28
+ }
29
+ function next() {
30
+ if (looks.length > 0) {
31
+ value = looks.shift();
32
+ return value.done ?? false;
33
+ }
34
+ value = cursor.next();
35
+ return value.done ?? false;
36
+ }
37
+ while (!next()) {
38
+ const char = value.value;
39
+ const charCode = char.codePointAt(0);
40
+ function isLF() {
41
+ if (charCode === LF)
42
+ return true;
43
+ if (charCode === CR) {
44
+ lookNext((c) => c.value.codePointAt(0) === LF);
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ switch (state) {
50
+ case 'stream':
51
+ state = 'event';
52
+ if (charCode === BOM)
53
+ break;
54
+ // tslint:disable-next-line: no-fallthrough --> intentional fallthrough
55
+ case 'event':
56
+ if (isLF()) {
57
+ yield temp;
58
+ temp = {};
59
+ }
60
+ else if (charCode === COLON) {
61
+ state = 'comment';
62
+ comment = '';
63
+ }
64
+ else {
65
+ state = 'field';
66
+ fieldName = char;
67
+ fieldValue = '';
68
+ }
69
+ break;
70
+ case 'comment':
71
+ if (isLF()) {
72
+ if (temp.comments === undefined) {
73
+ temp.comments = [];
74
+ }
75
+ temp.comments.push(comment);
76
+ comment = '';
77
+ state = 'event';
78
+ }
79
+ else {
80
+ comment += char;
81
+ }
82
+ break;
83
+ case 'field':
84
+ if (charCode === COLON) {
85
+ lookNext((c) => c.value.codePointAt(0) === SPACE);
86
+ state = 'field_value';
87
+ }
88
+ else if (isLF()) {
89
+ if (temp[fieldName] !== undefined)
90
+ temp[fieldName] += '\n';
91
+ else
92
+ temp[fieldName] = '';
93
+ fieldName = '';
94
+ fieldValue = '';
95
+ state = 'event';
96
+ }
97
+ else
98
+ fieldName += char;
99
+ break;
100
+ case 'field_value':
101
+ if (isLF()) {
102
+ if (temp[fieldName] !== undefined)
103
+ temp[fieldName] += '\n' + fieldValue;
104
+ else
105
+ temp[fieldName] = fieldValue;
106
+ fieldName = '';
107
+ fieldValue = '';
108
+ state = 'event';
109
+ }
110
+ else
111
+ fieldValue += char;
112
+ }
113
+ }
114
+ }
115
+ return (data) => parse(data);
116
+ }
117
+ exports.createParser = createParser;
5
118
  async function sse(response, onEvent = async () => { }, cacheCount = 0) {
6
119
  if (!(response instanceof ReadableStreamDefaultReader || response.ok)) {
7
120
  const error = await response.json().catch(() => ({}));
@@ -38,7 +151,7 @@ async function sse(response, onEvent = async () => { }, cacheCount = 0) {
38
151
  }
39
152
  exports.sse = sse;
40
153
  // eslint-disable-next-line generator-star-spacing
41
- async function* sseIterable(response, checkedFunction, mappedFunction, cacheCount = 0) {
154
+ async function* sseIterable(response) {
42
155
  if (!(response instanceof ReadableStreamDefaultReader) && !response.ok) {
43
156
  const error = await response.json().catch(() => ({}));
44
157
  throw new error_1.ChatLunaError(error_1.ChatLunaErrorCode.NETWORK_ERROR, new Error(`${response.status} ${response.statusText} ${JSON.stringify(error)}`));
@@ -47,61 +160,19 @@ async function* sseIterable(response, checkedFunction, mappedFunction, cacheCoun
47
160
  ? response
48
161
  : response.body.getReader();
49
162
  const decoder = new TextDecoder('utf-8');
50
- let bufferString = '';
51
- let tempCount = 0;
163
+ const parse = createParser();
52
164
  try {
53
165
  while (true) {
54
166
  const { value, done } = await reader.read();
55
- const decodeValue = decoder.decode(value, { stream: true });
56
- if (mappedFunction) {
57
- const mappedValue = mappedFunction(decodeValue);
58
- if (mappedValue instanceof Error) {
59
- throw mappedValue;
60
- }
61
- }
62
- bufferString += decodeValue;
63
- tempCount++;
64
- if (tempCount < cacheCount) {
65
- continue;
167
+ if (done) {
168
+ return '[DONE]';
66
169
  }
67
- if (bufferString.trim().length === 0) {
170
+ const decodeValue = decoder.decode(value, { stream: true });
171
+ if (decodeValue.trim().length === 0) {
68
172
  continue;
69
173
  }
70
- const splitted = bufferString
71
- .split('\n\n')
72
- .flatMap((item) => item.split('\n'));
73
- let currentTemp = {};
74
- for (let i = 0; i < splitted.length; i++) {
75
- const item = splitted[i];
76
- if (item.trim().length === 0) {
77
- continue;
78
- }
79
- // data: {aa:xx}
80
- // event:finish
81
- const [, type, data] = /(\w+):\s*(.*)$/g.exec(item) ?? [
82
- '',
83
- '',
84
- ''
85
- ];
86
- currentTemp[type] = data;
87
- if (type !== 'data') {
88
- continue;
89
- }
90
- if (checkedFunction) {
91
- const result = checkedFunction(data, currentTemp?.['event'], currentTemp);
92
- if (result) {
93
- yield data;
94
- }
95
- currentTemp = {};
96
- continue;
97
- }
98
- currentTemp = {};
99
- yield data;
100
- }
101
- bufferString = '';
102
- tempCount = 0;
103
- if (done) {
104
- return '[DONE]';
174
+ for (const event of parse(decodeValue)) {
175
+ yield event.data;
105
176
  }
106
177
  }
107
178
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna",
3
3
  "description": "chatluna for koishi",
4
- "version": "1.0.0-beta.39",
4
+ "version": "1.0.0-beta.40",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [