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.
- package/lib/utils/sse.d.ts +19 -1
- package/lib/utils/sse.js +122 -51
- package/package.json +1 -1
package/lib/utils/sse.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
51
|
-
let tempCount = 0;
|
|
163
|
+
const parse = createParser();
|
|
52
164
|
try {
|
|
53
165
|
while (true) {
|
|
54
166
|
const { value, done } = await reader.read();
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
170
|
+
const decodeValue = decoder.decode(value, { stream: true });
|
|
171
|
+
if (decodeValue.trim().length === 0) {
|
|
68
172
|
continue;
|
|
69
173
|
}
|
|
70
|
-
const
|
|
71
|
-
.
|
|
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
|
}
|