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

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>;
2
19
  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>;
20
+ export declare function rawSeeAsIterable(response: fetchType.Response | ReadableStreamDefaultReader<string>, cacheCount?: number): AsyncGenerator<string, void, unknown>;
21
+ export declare function sseIterable(response: fetchType.Response | ReadableStreamDefaultReader<string>): AsyncGenerator<SSEEvent, string, unknown>;
package/lib/utils/sse.js CHANGED
@@ -1,12 +1,127 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sseIterable = exports.sse = void 0;
3
+ exports.sseIterable = exports.rawSeeAsIterable = exports.sse = void 0;
4
4
  const error_1 = require("./error");
5
- async function sse(response, onEvent = async () => { }, cacheCount = 0) {
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
+ async function checkResponse(response) {
6
118
  if (!(response instanceof ReadableStreamDefaultReader || response.ok)) {
7
119
  const error = await response.json().catch(() => ({}));
8
120
  throw new error_1.ChatLunaError(error_1.ChatLunaErrorCode.NETWORK_ERROR, new Error(`${response.status} ${response.statusText} ${JSON.stringify(error)}`));
9
121
  }
122
+ }
123
+ async function sse(response, onEvent = async () => { }, cacheCount = 0) {
124
+ await checkResponse(response);
10
125
  const reader = response instanceof ReadableStreamDefaultReader
11
126
  ? response
12
127
  : response.body.getReader();
@@ -38,11 +153,8 @@ async function sse(response, onEvent = async () => { }, cacheCount = 0) {
38
153
  }
39
154
  exports.sse = sse;
40
155
  // eslint-disable-next-line generator-star-spacing
41
- async function* sseIterable(response, checkedFunction, mappedFunction, cacheCount = 0) {
42
- if (!(response instanceof ReadableStreamDefaultReader) && !response.ok) {
43
- const error = await response.json().catch(() => ({}));
44
- throw new error_1.ChatLunaError(error_1.ChatLunaErrorCode.NETWORK_ERROR, new Error(`${response.status} ${response.statusText} ${JSON.stringify(error)}`));
45
- }
156
+ async function* rawSeeAsIterable(response, cacheCount = 0) {
157
+ await checkResponse(response);
46
158
  const reader = response instanceof ReadableStreamDefaultReader
47
159
  ? response
48
160
  : response.body.getReader();
@@ -52,57 +164,51 @@ async function* sseIterable(response, checkedFunction, mappedFunction, cacheCoun
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;
167
+ if (done) {
168
+ if (bufferString.length > 0) {
169
+ yield bufferString;
60
170
  }
171
+ break;
61
172
  }
173
+ const decodeValue = decoder.decode(value, { stream: true });
62
174
  bufferString += decodeValue;
63
175
  tempCount++;
64
- if (tempCount < cacheCount) {
65
- continue;
66
- }
67
- if (bufferString.trim().length === 0) {
68
- continue;
69
- }
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;
176
+ if (tempCount > cacheCount) {
177
+ yield bufferString;
178
+ bufferString = '';
179
+ tempCount = 0;
100
180
  }
101
- bufferString = '';
102
- tempCount = 0;
181
+ }
182
+ }
183
+ finally {
184
+ reader.releaseLock();
185
+ }
186
+ }
187
+ exports.rawSeeAsIterable = rawSeeAsIterable;
188
+ // eslint-disable-next-line generator-star-spacing
189
+ async function* sseIterable(response) {
190
+ if (!(response instanceof ReadableStreamDefaultReader) && !response.ok) {
191
+ const error = await response.json().catch(() => ({}));
192
+ throw new error_1.ChatLunaError(error_1.ChatLunaErrorCode.NETWORK_ERROR, new Error(`${response.status} ${response.statusText} ${JSON.stringify(error)}`));
193
+ }
194
+ const reader = response instanceof ReadableStreamDefaultReader
195
+ ? response
196
+ : response.body.getReader();
197
+ const decoder = new TextDecoder('utf-8');
198
+ const parse = createParser();
199
+ try {
200
+ while (true) {
201
+ const { value, done } = await reader.read();
103
202
  if (done) {
104
203
  return '[DONE]';
105
204
  }
205
+ const decodeValue = decoder.decode(value, { stream: true });
206
+ if (decodeValue.trim().length === 0) {
207
+ continue;
208
+ }
209
+ for (const event of parse(decodeValue)) {
210
+ yield event;
211
+ }
106
212
  }
107
213
  }
108
214
  finally {
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.41",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [