@xsai/stream-text 0.3.5 → 0.4.0-beta.10
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/README.md +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +85 -105
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://xsai.js.org/docs/packages/stream/text
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WithUnknown } from '@xsai/shared';
|
|
1
2
|
import { CompletionToolCall, CompletionToolResult, FinishReason, Usage, ChatOptions, CompletionStep, Message } from '@xsai/shared-chat';
|
|
2
3
|
|
|
3
4
|
type StreamTextEvent = (CompletionToolCall & {
|
|
@@ -16,6 +17,9 @@ type StreamTextEvent = (CompletionToolCall & {
|
|
|
16
17
|
finishReason: FinishReason;
|
|
17
18
|
type: 'finish';
|
|
18
19
|
usage?: Usage;
|
|
20
|
+
} | {
|
|
21
|
+
text: string;
|
|
22
|
+
type: 'reasoning-delta';
|
|
19
23
|
} | {
|
|
20
24
|
text: string;
|
|
21
25
|
type: 'text-delta';
|
|
@@ -48,8 +52,10 @@ interface StreamTextResult {
|
|
|
48
52
|
messages: Promise<Message[]>;
|
|
49
53
|
steps: Promise<CompletionStep[]>;
|
|
50
54
|
textStream: ReadableStream<string>;
|
|
55
|
+
totalUsage: Promise<undefined | Usage>;
|
|
51
56
|
usage: Promise<undefined | Usage>;
|
|
52
57
|
}
|
|
53
|
-
declare const streamText: (options: StreamTextOptions) =>
|
|
58
|
+
declare const streamText: (options: WithUnknown<StreamTextOptions>) => StreamTextResult;
|
|
54
59
|
|
|
55
|
-
export {
|
|
60
|
+
export { streamText };
|
|
61
|
+
export type { StreamTextEvent, StreamTextOptions, StreamTextResult };
|
package/dist/index.js
CHANGED
|
@@ -1,48 +1,6 @@
|
|
|
1
|
+
import { DelayedPromise, trampoline, objCamelToSnake } from '@xsai/shared';
|
|
1
2
|
import { chat, executeTool, determineStepType } from '@xsai/shared-chat';
|
|
2
3
|
|
|
3
|
-
const strCamelToSnake = (str) => str.replace(/[A-Z]/g, (s) => `_${s.toLowerCase()}`);
|
|
4
|
-
const objCamelToSnake = (obj) => Object.fromEntries(Object.entries(obj).map(([k, v]) => [strCamelToSnake(k), v]));
|
|
5
|
-
|
|
6
|
-
const trampoline = async (fn) => {
|
|
7
|
-
let result = await fn();
|
|
8
|
-
while (result instanceof Function)
|
|
9
|
-
result = await result();
|
|
10
|
-
return result;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
class DelayedPromise {
|
|
14
|
-
get promise() {
|
|
15
|
-
if (this._promise == null) {
|
|
16
|
-
this._promise = new Promise((resolve, reject) => {
|
|
17
|
-
if (this.status.type === "resolved") {
|
|
18
|
-
resolve(this.status.value);
|
|
19
|
-
} else if (this.status.type === "rejected") {
|
|
20
|
-
reject(this.status.error);
|
|
21
|
-
}
|
|
22
|
-
this._resolve = resolve;
|
|
23
|
-
this._reject = reject;
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return this._promise;
|
|
27
|
-
}
|
|
28
|
-
_promise;
|
|
29
|
-
_reject;
|
|
30
|
-
_resolve;
|
|
31
|
-
status = { type: "pending" };
|
|
32
|
-
reject(error) {
|
|
33
|
-
this.status = { error, type: "rejected" };
|
|
34
|
-
if (this._promise) {
|
|
35
|
-
this._reject?.(error);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
resolve(value) {
|
|
39
|
-
this.status = { type: "resolved", value };
|
|
40
|
-
if (this._promise) {
|
|
41
|
-
this._resolve?.(value);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
4
|
const parseChunk = (text) => {
|
|
47
5
|
if (!text || !text.startsWith("data:"))
|
|
48
6
|
return [void 0, false];
|
|
@@ -82,14 +40,16 @@ const transformChunk = () => {
|
|
|
82
40
|
});
|
|
83
41
|
};
|
|
84
42
|
|
|
85
|
-
const streamText =
|
|
43
|
+
const streamText = (options) => {
|
|
86
44
|
const steps = [];
|
|
87
45
|
const messages = structuredClone(options.messages);
|
|
88
46
|
const maxSteps = options.maxSteps ?? 1;
|
|
89
47
|
let usage;
|
|
48
|
+
let totalUsage;
|
|
90
49
|
const resultSteps = new DelayedPromise();
|
|
91
50
|
const resultMessages = new DelayedPromise();
|
|
92
51
|
const resultUsage = new DelayedPromise();
|
|
52
|
+
const resultTotalUsage = new DelayedPromise();
|
|
93
53
|
let eventCtrl;
|
|
94
54
|
let textCtrl;
|
|
95
55
|
const eventStream = new ReadableStream({ start: (controller) => eventCtrl = controller });
|
|
@@ -102,9 +62,21 @@ const streamText = async (options) => {
|
|
|
102
62
|
steps.push(step);
|
|
103
63
|
void options.onStepFinish?.(step);
|
|
104
64
|
};
|
|
105
|
-
const
|
|
65
|
+
const doStream = async () => {
|
|
66
|
+
const { body: stream } = await chat({
|
|
67
|
+
...options,
|
|
68
|
+
maxSteps: void 0,
|
|
69
|
+
messages,
|
|
70
|
+
stream: true,
|
|
71
|
+
streamOptions: options.streamOptions != null ? objCamelToSnake(options.streamOptions) : void 0
|
|
72
|
+
});
|
|
106
73
|
const pushUsage = (u) => {
|
|
107
74
|
usage = u;
|
|
75
|
+
totalUsage = totalUsage ? {
|
|
76
|
+
completion_tokens: totalUsage.completion_tokens + u.completion_tokens,
|
|
77
|
+
prompt_tokens: totalUsage.prompt_tokens + u.prompt_tokens,
|
|
78
|
+
total_tokens: totalUsage.total_tokens + u.total_tokens
|
|
79
|
+
} : { ...u };
|
|
108
80
|
};
|
|
109
81
|
let text = "";
|
|
110
82
|
const pushText = (content) => {
|
|
@@ -115,55 +87,58 @@ const streamText = async (options) => {
|
|
|
115
87
|
const toolCalls = [];
|
|
116
88
|
const toolResults = [];
|
|
117
89
|
let finishReason = "other";
|
|
118
|
-
await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (choice.delta.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
90
|
+
await stream.pipeThrough(transformChunk()).pipeTo(new WritableStream({
|
|
91
|
+
abort: (reason) => {
|
|
92
|
+
eventCtrl?.error(reason);
|
|
93
|
+
textCtrl?.error(reason);
|
|
94
|
+
},
|
|
95
|
+
close: () => {
|
|
96
|
+
},
|
|
97
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
98
|
+
write: (chunk) => {
|
|
99
|
+
if (chunk.usage)
|
|
100
|
+
pushUsage(chunk.usage);
|
|
101
|
+
if (chunk.choices == null || chunk.choices.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
const choice = chunk.choices[0];
|
|
104
|
+
if (choice.delta.reasoning_content != null)
|
|
105
|
+
pushEvent({ text: choice.delta.reasoning_content, type: "reasoning-delta" });
|
|
106
|
+
if (choice.finish_reason != null)
|
|
107
|
+
finishReason = choice.finish_reason;
|
|
108
|
+
if (choice.delta.tool_calls?.length === 0 || choice.delta.tool_calls == null) {
|
|
109
|
+
if (choice.delta.content != null) {
|
|
110
|
+
pushEvent({ text: choice.delta.content, type: "text-delta" });
|
|
111
|
+
pushText(choice.delta.content);
|
|
112
|
+
} else if (choice.delta.refusal != null) {
|
|
113
|
+
pushEvent({ error: choice.delta.refusal, type: "error" });
|
|
114
|
+
} else if (choice.finish_reason != null) {
|
|
115
|
+
pushEvent({ finishReason: choice.finish_reason, type: "finish", usage });
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
for (const toolCall of choice.delta.tool_calls) {
|
|
119
|
+
const { index } = toolCall;
|
|
120
|
+
if (!tool_calls.at(index)) {
|
|
121
|
+
tool_calls[index] = {
|
|
122
|
+
...toolCall,
|
|
123
|
+
function: {
|
|
124
|
+
...toolCall.function,
|
|
125
|
+
arguments: toolCall.function.arguments ?? ""
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
pushEvent({ toolCallId: toolCall.id, toolName: toolCall.function.name, type: "tool-call-streaming-start" });
|
|
129
|
+
} else {
|
|
130
|
+
tool_calls[index].function.arguments += toolCall.function.arguments;
|
|
131
|
+
pushEvent({ argsTextDelta: toolCall.function.arguments, toolCallId: toolCall.id, toolName: toolCall.function.name ?? tool_calls[index].function.name, type: "tool-call-delta" });
|
|
159
132
|
}
|
|
160
133
|
}
|
|
161
134
|
}
|
|
162
|
-
}
|
|
163
|
-
);
|
|
135
|
+
}
|
|
136
|
+
}));
|
|
164
137
|
messages.push({ content: text, role: "assistant", tool_calls });
|
|
165
138
|
if (tool_calls.length !== 0) {
|
|
166
139
|
for (const toolCall of tool_calls) {
|
|
140
|
+
if (toolCall == null)
|
|
141
|
+
continue;
|
|
167
142
|
const { completionToolCall, completionToolResult, message } = await executeTool({
|
|
168
143
|
abortSignal: options.abortSignal,
|
|
169
144
|
messages,
|
|
@@ -192,29 +167,34 @@ const streamText = async (options) => {
|
|
|
192
167
|
usage
|
|
193
168
|
});
|
|
194
169
|
if (toolCalls.length !== 0 && steps.length < maxSteps)
|
|
195
|
-
return async () =>
|
|
170
|
+
return async () => doStream();
|
|
196
171
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
172
|
+
void (async () => {
|
|
173
|
+
try {
|
|
174
|
+
await trampoline(async () => doStream());
|
|
175
|
+
eventCtrl?.close();
|
|
176
|
+
textCtrl?.close();
|
|
177
|
+
} catch (err) {
|
|
178
|
+
eventCtrl?.error(err);
|
|
179
|
+
textCtrl?.error(err);
|
|
180
|
+
resultSteps.reject(err);
|
|
181
|
+
resultMessages.reject(err);
|
|
182
|
+
resultUsage.reject(err);
|
|
183
|
+
resultTotalUsage.reject(err);
|
|
184
|
+
} finally {
|
|
185
|
+
resultSteps.resolve(steps);
|
|
186
|
+
resultMessages.resolve(messages);
|
|
187
|
+
resultUsage.resolve(usage);
|
|
188
|
+
resultTotalUsage.resolve(totalUsage);
|
|
189
|
+
void options.onFinish?.(steps.at(-1));
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
213
192
|
return {
|
|
214
193
|
fullStream: eventStream,
|
|
215
194
|
messages: resultMessages.promise,
|
|
216
195
|
steps: resultSteps.promise,
|
|
217
196
|
textStream,
|
|
197
|
+
totalUsage: resultTotalUsage.promise,
|
|
218
198
|
usage: resultUsage.promise
|
|
219
199
|
};
|
|
220
200
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xsai/stream-text",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0-beta.10",
|
|
5
5
|
"description": "extra-small AI SDK.",
|
|
6
6
|
"author": "Moeru AI",
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"dist"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@xsai/shared
|
|
32
|
+
"@xsai/shared": "~0.4.0-beta.10",
|
|
33
|
+
"@xsai/shared-chat": "~0.4.0-beta.10"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"valibot": "^1.0.0",
|
|
36
|
-
"@xsai/
|
|
37
|
-
"@xsai/tool": "~0.3.5"
|
|
37
|
+
"@xsai/tool": "~0.4.0-beta.10"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "pkgroll",
|