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.
- package/lib/utils/sse.d.ts +19 -1
- package/lib/utils/sse.js +156 -50
- 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>;
|
|
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
|
|
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
|
-
|
|
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*
|
|
42
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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 {
|